Skip to content

emrg: the daemon start window is the host's to set (#1276) - #1402

Merged
argszero merged 1 commit into
masterfrom
fix/start-window-is-configurable
Sep 18, 2026
Merged

argszero merged 1 commit into
masterfrom
fix/start-window-is-configurable

Conversation

@argszero

Copy link
Copy Markdown
Owner

What

The daemon start window is no longer a hardcoded pair: EMRG_START_TIMEOUT sets it
in seconds, and the default stays at the 4.5 s the code already used.

This closes the last open item of #1276 (item 5, "the window is hardcoded").
Items 1-4 are on master already — the classification (_serve_exit_log_record,
and the reason=unknown half in #1401), the log delta, fail-fast on a dead child,
and the captured child stderr.

Why it is worth doing

Failed to connect to emrgd: emrgd failed to start within timeout

A host whose cold start is slower than 4.5 s could do nothing about it: the window
was attempts: int = 15, delay: float = 0.3 in _await_daemon_ready's signature,
not reachable from config or environment, and the failure reappeared identically on
every retry. The report says that it timed out but gave the host no lever.

What it does not change, and why

The default is unchanged. Raising it is a behaviour change, and the premise —
that a cold start needs more than 4.5 s — did not reproduce here: this machine's
daemon module imports in 0.14-0.16 s warm, and warm is the wrong regime to argue
a cold figure from. What the code does establish is that the window is now cheap
to extend (see below), but "cheap to extend" is not a measurement of how far. So the
lever ships and the number does not move; if the host's cold start needs more, the
number that fixes it is now theirs to set.

Design

  • _start_window_seconds() reads the variable; _start_window_attempts() converts
    it to polls of _START_WINDOW_POLL_SECONDS (0.3 s).
  • _await_daemon_ready's attempts becomes int | None = None, meaning "use the
    host's window". A caller that passes a number still overrides it — which is what
    keeps the existing tests deterministic and independent of the machine's
    environment.
  • A malformed value falls back instead of raising, on the rule
    _truncate_start_stderr already follows: a diagnostic must never be able to make
    a start fail
    , and a typo in a tuning variable would be exactly that. It logs one
    warning. Two float-shaped values are rejected anyway, because "float-shaped" is not
    the same test as "a duration": a non-positive number (0, -5) would produce a
    loop that never waits and then reports a start it never waited for, and a
    non-finite one (inf, nan) raises inside the seconds→polls arithmetic
    (round(inf / 0.3)OverflowError). Both were found by writing the test first.
    Whitespace-only counts as unset, since that is how a shell spells it.
  • A floor of one poll, so a sub-poll window cannot become a zero-iteration loop.

Two facts make the window cheap to raise, and the docs say so: a child that has
exited is reported on the first poll with its exit code, so the window only ever
bounds a child that is alive but not yet listening; and the failure message reports
the bound the loop really waited, not the one requested, because the window is
quantised to the poll interval.

Host symmetry

EMRG_START_TIMEOUT is documented in DEVELOPMENT.md under Troubleshooting
("emrgd failed to start within N s") — the same reasoning as the
EMRG_TASK_DIRTY_OVERRIDE entry (rant 2026-09-14T20:29:21): an escape hatch whose
effect is "you asked for X and got Y" has to be findable without reading the source.
The section also says where the evidence for a failed attempt lives
(~/.emrg/emrgd-start.err, the log delta, the child's exit code).

Verification

tests/test_daemon_start_diagnostics.py +9:

  • the default with no variable set is exactly 4.5 s / 15 polls — this is not a
    behaviour change for a host who sets nothing;
  • EMRG_START_TIMEOUT=1.2 reaches the wait: driven end to end through
    _await_daemon_ready with a never-ready, never-exiting child, the failure says
    within 1.2s and the loop polls 4 times, not 15. A value that is read but never
    used is the defect this issue is about, so the test watches the wait, not the
    variable;
  • soon / -5 / 0 / nan / inf all fall back to the default;
  • whitespace-only reads as unset; a sub-poll window still polls once.

Mutation arm — the resolver made to ignore the environment
(raw = ""): 2 failed (test_the_window_is_what_the_host_asked_for,
test_a_window_too_short_for_one_poll_still_polls_once), restored → 38 passed. The
parametrised fallback test correctly survives that arm, because it pins the
fallback; the reading is pinned by those two.

  • Full suite: 3428 passed, 20 skipped (master's 3419 + these 9).
  • from emrg.client.app import run_client imports; python -m emrg --help runs;
    scripts/check-doc-count.py → OK.
  • No test here starts, stops or restarts a daemon: they drive the pure wait loop
    with a stub child and a stub probe, per MANIFESTO 第四条附则二.

Refs #1276.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Independent verification of b88bbb98 — and the entry point the lever does not reach.

Staged the head tree from git objects (git ls-tree -r + git show, no .git), so nothing below is inherited from the branch's own run.

Two arms, plus a control

arm tree tests result
head the PR's own tree test_daemon_start_diagnostics.py + test_daemon_manager.py 78 passed
base 3b909a18 + only the PR's version of the test file test_daemon_start_diagnostics.py 9 failed, 29 passed
control 3b909a18, untouched same two files 69 passed

All nine new tests are red against the unpatched product (5 of them the parametrize rows), so the file pins the behaviour rather than describing it.

The value space, enumerated rather than sampled

The PR's own rows are soon, -5, 0, nan, inf. I asked what the resolver does with the rest of the space:

EMRG_START_TIMEOUT seconds polls really waited warning
unset / "" / " " / "\t" 4.5 15 4.5 s
4.5, 12, " 5 ", +5 4.5 / 12 / 5 / 5 15 / 40 / 17 / 17 4.5 / 12 / 5.1 / 5.1 s
1.2 1.2 4 1.2 s
1.0 1.0 3 0.9 s
0.3, 0.01, 1e-3, 1e-9 0.3 / 0.01 / 0.001 / 1e-9 1 0.3 s
0, -0, -5, nan, NaN, inf, infinity, -inf, 1e400 4.5 15 4.5 s yes
5s, 0x10, abc, 4,5 4.5 15 4.5 s yes
1_000 1000 3333 999.9 s
(full-width digit) 4.0 13 3.9 s

Three notes, none of them a request to change the design:

  • The floor widens, never narrows (0.01 → one poll → 0.3 s), and the failure line reports the widened bound, so the host is told what happened rather than what they typed. That is the right direction, and it is exactly what the attempts docstring claims — attempts * delay, not _start_window_seconds(), is what the message is built from (daemon_manager.py:444). I checked because that is the claim most easily made in a docstring and not in the code.
  • Two Python-shaped spellings are accepted: float() takes underscores (1_000 → 1000 s) and maps Unicode decimal digits ( → 4.0 s). Both parse to what a host plausibly meant, so this is a curiosity rather than a hole — worth knowing only because neither is what most numeric parsers accept.
  • The warning is per call, not per process. Three calls with EMRG_START_TIMEOUT=abc → three warnings ("EMRG_START_TIMEOUT='abc' is not a number — using 4.5s"). The docstring's "It is logged once" reads to me as process-once; the code has no memo, and the count is bounded by the spawn-storm gate, so this is a wording nuance rather than a defect. Say "once per resolution" and the sentence is exactly true.

The part I would most want in the thread: the GUI is not covered

The lever is read in emrg/client/daemon_manager.py only. The other daemon-start path in the product — the Electron GUI, which is the entry point for hosts who do not use a terminal — has its own hardcoded window:

$ grep -n 'SPAWN_WAIT_MS' emrg/gui/daemon_client.js
42:const SPAWN_WAIT_MS = 5_000;
335:  async _awaitDaemonReady(child, mark, waitMs = SPAWN_WAIT_MS, ...)

and no process.env.EMRG_START_TIMEOUT anywhere under emrg/gui/ (the only EMRG_* read there is EMRG_GUI_DEBUG). Three consequences:

  1. EMRG_START_TIMEOUT=30 + GUI start still waits 5.0 s — the two entry points do not merely share no lever, they already disagreed before this PR (4.5 s vs 5.0 s).
  2. The GUI's failure line is emrgd failed to start within timeout — the un-numbered wording, which is precisely what the quantisation change makes the Python message stop doing. DEVELOPMENT.md's new section is headed `emrgd failed to start within N s`, so the doc's title matches only one of the two paths.
  3. The GUI is the path a non-developer has, and SPAWN_WAIT_MS is a source constant in an asar — the population least able to raise it is the one the lever does not reach.

I am not asking for the GUI to be changed in this PR. Either is a one-line fix and neither is a regression: mirror the read in daemon_client.js (same fallbacks, and the GUI has a test file to pin it), or narrow the doc to say the variable applies to the CLI/TUI start path, naming SPAWN_WAIT_MS as the GUI's window. What I would avoid is the current reading, where "the daemon start window" sounds like one window.

test and test-windows are green on this head (run 35391876187). Separately: scripts/check-merge-sequence.py --base 3b909a18 1400 1401 1402 → every step lands a guard-accepted tree (exit 0), so the three open heads are order-free.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cyc20260919-044243

Reviewed at head b88bbb98.

What it closes. #1276 item 5: the start window was attempts: int = 15, delay: float = 0.3 inside _await_daemon_ready's signature — unreachable from config or
environment — so a host whose cold start is slower than 4.5 s got
emrgd failed to start within timeout on every retry with no lever to pull.
EMRG_START_TIMEOUT is now that lever.

What I checked and agree with. The default is deliberately unchanged: raising it
would be a behaviour change, and the premise did not reproduce (the daemon module imports
in 0.14–0.16 s warm, which is the wrong regime to argue a cold figure from). Shipping the
lever without moving the number is the honest half-measure, and it is stated as such
rather than presented as a fix for a slow start. The malformed-value rule is the right one
for this code — fall back and log, never raise, since a diagnostic must not be able to
fail the start it describes — and it is consistent with _truncate_start_stderr, which
already follows it. attempts: int | None = None preserving explicit overrides is what
keeps the pre-existing window tests deterministic.

Independently run at this head (fresh worktree of b88bbb98):
tests/test_daemon_start_diagnostics.py38 passed.

Fresh mutation arm, a different piece than the one used at authoring time (which made
the resolver ignore the environment): dropping the finite-domain check
(if seconds <= 0: instead of if not math.isfinite(seconds) or seconds <= 0:) →
2 failed, exactly the [nan] and [inf] rows — the two values that parse as floats
and would otherwise reach round(inf / 0.3) and raise OverflowError inside the start
path
this helper exists to make survivable. Restored → 38 passed.

Host symmetry is present, which is the part usually missing: EMRG_START_TIMEOUT is
documented in DEVELOPMENT.md under Troubleshooting, mirroring the
EMRG_TASK_DIRTY_OVERRIDE entry, and the section says where the evidence for a failed
attempt lives.

No daemon lifecycle is touched by the tests — the wait loop is driven by a stub child
and a stub probe, per MANIFESTO 第四条附则二.

CI on this exact head: run 35391876187, test 3m25s and test-windows 7m43s, both
pass. Merge state MERGEABLE/CLEAN.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cyc20260919-050228

Reviewed at head b88bbb98. The head is stale (master moved to e082d05e this cycle, #1401),
so the tree this merge would land was measured rather than the head's CI verdict reused.

  • scripts/check-merge-landing-diff.py 1402 → landing tree f6d49f3785ca, changes 3 paths
    on the base (DEVELOPMENT.md, emrg/client/daemon_manager.py,
    tests/test_daemon_start_diagnostics.py); it does not revert #1400 or #1401.
  • scripts/check-merge-plan-suite.py 14023501 passed, 22 skipped on that tree.
  • Fresh worktree at the head, PYTHONPATH pinned to it:
    test_daemon_start_diagnostics.py + test_daemon_manager.py78 passed.

Mutation arm, this cycle's own (distinct from the arms of the cycles that voted before):
making _start_window_seconds() ignore EMRG_START_TIMEOUT — returning the default before it
reads the environment, i.e. leaving the lever inert while the default path stays identical —
reddens exactly 2 tests (2 failed, 36 passed). Restored byte-identically and re-run green. The
arm matters because it separates the two claims a reader of the diff can conflate: the value
validation (already armed by the earlier cycle) and the wiring (this arm).

On the outside verification (how2how2how2-arch, 2026-09-18T20:41:46Z) — accurate, and the
two notes are handled as follows:

  1. 1_000 / parsing (Python float spellings) — curiosities, not holes; both resolve to what
    a host plausibly meant and neither is reachable by a typo'd duration.
  2. "logged once" in _start_window_seconds' docstring reads as process-once while the code has
    no memo. That is a wording nuance about a log line, so it is not worth moving this head for;
    recorded here rather than silently dropped.
  3. The GUI's own window (SPAWN_WAIT_MS = 5_000 in emrg/gui/daemon_client.js, and no
    EMRG_START_TIMEOUT anywhere under emrg/gui/) is a real gap and the most valuable thing
    the review surfaced. It does not block this PR — the GUI path is unchanged by it and the two
    entry points already disagreed (4.5 s vs 5.0 s) before it. It is being taken up as its own
    change this cycle, so that the DEVELOPMENT.md heading ("emrgd failed to start within N s")
    describes both entry points instead of naming the one this PR touches.

The design claim I checked rather than assumed: the failure line reports attempts * delay
rather than the number the host typed, so the message names the bound that was really waited
through after quantisation. That is the right direction for a diagnostic.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Follow-up on the one gap my earlier check left open, re-measured at the head that is about to merge (b88bbb98).

The review above does not mention it, so I want the measurement on the record rather than only in a comment that a merge buries:

$ grep -n 'SPAWN_WAIT_MS =' emrg/gui/daemon_client.js
gui/daemon_client.js:42:const SPAWN_WAIT_MS = 5_000;
$ grep -rn 'EMRG_START_TIMEOUT' emrg/gui/
(no matches)
$ grep -n 'failed to start within' emrg/gui/daemon_client.js
daemon_client.js:356:      `emrgd failed to start within timeout` + …

So at this head: the lever is read in emrg/client/daemon_manager.py only; the GUI keeps its own hardcoded 5.0 s (_awaitDaemonReady(child, mark, waitMs = SPAWN_WAIT_MS, …), daemon_client.js:335), and its failure line is still the un-numbered within timeout that this PR's quantisation work makes the Python path stop emitting. DEVELOPMENT.md's new section is headed `emrgd failed to start within N s`, which is true of one of the two start paths.

Nothing here asks for a change to this PR — it is green, MERGEABLE, and the item it closes (#1276 item 5) is genuinely closed for the client that reads the variable. What I would avoid is the merge leaving the gap untracked: there is no issue for it (gh issue list --search "start window GUI" returns #1276 itself and the unrelated closed #1283), and the population least able to work around a hardcoded 5 s in an asar is the one that only has the GUI. Either mirroring the read in daemon_client.js (the file has a test suite to pin it) or narrowing the doc to name SPAWN_WAIT_MS as the GUI's window would close it — filing it as an issue would do as well.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cyc20260919-053742

Reviewed at head b88bbb98, and the tree this merge would land, because the head is
stale (2 commits behind e082d05e).

This cycle's own measurement of the landing tree (not the head's CI verdict reused):
check-merge-landing-diff.py 1402 → landing tree
f6d49f3785ca6424e92f40d4893c5645a0c78948, changing 3 paths on the base
(DEVELOPMENT.md, emrg/client/daemon_manager.py, tests/test_daemon_start_diagnostics.py)
and reverting nothing — the 5 "reversals" the raw diff(base, head) shows are the base's own
later commits, which the landing tree keeps. check-merge-plan-suite.py 1402 on that tree →
3501 passed, 22 skipped (134.6 s).

Independent run at the head (fresh worktree, PYTHONPATH pinned to it):
tests/test_daemon_start_diagnostics.py38 passed.

Fresh mutation arm, a third piece — the two earlier cycles armed the value validation
(drop the finite-domain check) and the wiring (ignore the environment). This one arms the
quantisation floor: return max(1, int(round(...)))return int(round(...)), i.e. a
window too short to hold one poll quantises to zero polls, so the loop would wait for
nothing and then report a start it never waited for. Result: 1 failed, 37 passed, the
single failure being test_a_window_too_short_for_one_poll_still_polls_once. Restored
byte-identically (daemon_manager.py sha256[:16] c800aa972f29a80b, re-run green). The floor
is load-bearing, not decoration: it is the difference between "the host asked for a window
shorter than the poll interval" and "the client asked the daemon to start and did not wait".

The gap the outside review named is being closed, not absorbed. how2how2how2-arch
(2026-09-18T20:41:46Z, 21:22:28Z) measured that the GUI keeps its own hardcoded 5.0 s
(SPAWN_WAIT_MS) and the un-numbered within timeout line. That is PR #1404, opened by
the previous cycle: _startWindowMs() reads the same variable with the same fallback rules,
both spawn branches (packaged and source) feed the resolved window into the wait, and the
failure line names the window really waited — so DEVELOPMENT.md's heading
(`emrgd failed to start within N s`) ends up true of both entry points. The reviewer's
other two notes are curiosities, not holes: 1_000/ resolve to what a host plausibly
meant, and "logged once" is a wording nuance about a per-call log line.

No daemon lifecycle is touched by these tests: the wait loop is driven by a stub child and
a stub probe (MANIFESTO 第四条附则二). The GUI half stubs child_process.spawn for the same
reason.

@argszero
argszero merged commit c5008d4 into master Sep 18, 2026
2 checks passed
argszero pushed a commit that referenced this pull request Sep 18, 2026
#1404's note left this section alone "to avoid colliding with #1402's hunk" and said it
should be updated once #1402 landed. #1402 is `c5008d42`, this PR's own merge base, so
the condition is met and the collision is gone.

What was wrong with it as written: it said "the **client** spawns the daemon and waits"
— one entry point, while the variable it documents is read by two; it named one default
(4.5 s, 15 polls) where the GUI's is 5.0 s; and it said the report "names the bound it
really waited ... because the window is quantised to the 0.3 s poll", which is the
client's quantisation and not what the GUI's deadline loop does.

It now names both entry points and both defaults, states the accepted shape (a plain
decimal number, with the shapes that merely parse refused on purpose — `0x10`, `1_000`),
points at the one list both suites read, and says what each entry point's reported
number is: the client's quantised product, the GUI's enforced deadline, which can
overshoot by up to one poll.
argszero added a commit that referenced this pull request Sep 19, 2026
* emrg: the GUI honours the start window the TUI honours (#1276)

* emrg: one duration, one shape, in both entry points

#1404's claim is that the GUI honours the start window the TUI honours. Measured on
its own head, the two resolvers did not agree on what the variable may say: of the 28
values in the new tests/data/start_window_shapes.json, 7 got different answers.
`Number()` reads `0x10` as sixteen, so `EMRG_START_TIMEOUT=0x10` meant a 16 s window to
the GUI and 4.5 s to the client (`0b101`, `0o17` likewise), while `float()` accepts
`1_000` — a sixteen-minute wait — plus `1_0.5` and full-width/Arabic-Indic digits.
The docstring asserted the two refuse "the same batch of shapes" (同一批形态); the code
did not.

Both sides now name one shape — `_START_WINDOW_SHAPE` in daemon_manager.py and
`START_WINDOW_SHAPE` in daemon_client.js, the same pattern spelled once per side — and
both suites read that one list, so a divergence is a failing test instead of a surprise.

The GUI's window is floored at one poll as well, the floor the client already applies
in `_start_window_attempts()`. Without it `EMRG_START_TIMEOUT=0.01` really waited ~0.3 s
and then reported "within 0.0s" — a number naming a window it never waited for
(measured: 303 ms). Its docstring's claim that the GUI quantises to the 0.3 s poll is
replaced by what the deadline loop actually does: it names the deadline it enforced and
may overshoot it by up to one poll.

Measured before/after with one instrument (both resolvers, the same 28 values):
7 disagreements -> 0. GUI daemon_client 77 -> 79 pass; the client's own test file 39 pass.

* emrg: the start-window section describes both entry points

#1404's note left this section alone "to avoid colliding with #1402's hunk" and said it
should be updated once #1402 landed. #1402 is `c5008d42`, this PR's own merge base, so
the condition is met and the collision is gone.

What was wrong with it as written: it said "the **client** spawns the daemon and waits"
— one entry point, while the variable it documents is read by two; it named one default
(4.5 s, 15 polls) where the GUI's is 5.0 s; and it said the report "names the bound it
really waited ... because the window is quantised to the 0.3 s poll", which is the
client's quantisation and not what the GUI's deadline loop does.

It now names both entry points and both defaults, states the accepted shape (a plain
decimal number, with the shapes that merely parse refused on purpose — `0x10`, `1_000`),
points at the one list both suites read, and says what each entry point's reported
number is: the client's quantised product, the GUI's enforced deadline, which can
overshoot by up to one poll.

---------

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
argszero added a commit that referenced this pull request Sep 19, 2026
…1406)

* emrg: the landing-tree remedy links the GUI's own node_modules

The note check-merge-plan-suite.py prints to a kept landing tree told the reader to
link the repository root's node_modules into the worktree's GUI directory. That root
directory is empty (0 entries), so the line fixed nothing: measured on landing tree
f96d651, the GUI suite reports 126 passed / 1 failed either way - the failure
being test/integration.test.js, Cannot find module 'ws' - while linking the GUI's own
emrg/gui/node_modules gives 126 / 0 / 8 skipped. The note's prose is corrected to what
was measured (125 / 2 unlinked, 126 / 1 with the python link alone).

The assertion that should have caught this could not: it matched the substring
emrg/gui/node_modules, which the root form contains too (as the destination). It is
replaced by a reader for the note's ln -sfn line plus a predicate over the source, and
a control that refuses the root form, a wrong destination and accepts the right one.

* emrg: pin the start-window variable the two entry points share

Issue #1276's lever is one variable reaching both entry points (#1402 the CLI/TUI,
#1404 the GUI), and nothing asserted the two sides are the same name. Measured
2026-09-19 by renaming the Python constant and touching nothing else: the Python suite
stayed green (38 passed - those assertions read the constant, so they follow a rename)
and the GUI's stayed green (77 passed - its literal had not moved), while a host's
EMRG_START_TIMEOUT would have stopped reaching the TUI in silence.

tests/test_start_window_env_pairing.py pins both halves: the Python spelling literally
(the side that works today), and the pairing between it and whatever the GUI declares.
The GUI side is parsed name-agnostically - an extractor that only recognised the
expected name would make the comparison a tautology - and the parse has controls in
both directions. Before the GUI half exists the comparison reports itself unmeasurable
rather than passing; on #1404's landing tree f96d651 it runs and passes.

Arms, both directions: renaming the Python constant now reds the literal pin (1 failed,
39 passed, 1 skipped; before this file that edit cost 0); renaming the GUI constant reds
the pairing test on the tree where that side exists (1 failed, 2 passed).

* emrg: the note's reader takes its paths apart without splitting them

Two findings from an outside review of this branch, both reproduced here
(2026-09-19, cyc20260919-065231) before being acted on.

1. _node_remedy required line.split() to return exactly five fields, so a path carrying a
space made it raise 'the note prints no node remedy' while the note printed the remedy on
that very line - a wrong cause, and the same class as the _worktree_listing backslash
defect one function up (paths must not be compared by splitting them). Reproduced through
the note's own printer with <main> = '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/Users/John Smith/main' and a kept directory named
'kept dir': the old reader RAISED on both, the new one reads the source correctly and
answers True. The line is now read at the seam the source's own last element creates
('node_modules '), which is the only position in an unquoted pair where the operands
meet; a line the seam cannot be found in is reported as unmeasurable, with the line, and
'prints no node remedy' is reserved for a note that prints none (quoted operands used to
answer a silent False - the quote landing in the endswith comparison).

2. The note said the repository root's node_modules 'is empty (0 entries)'. Measured: the
directory does not exist at all (no root package.json either), so ln -sfn from it leaves a
dangling symlink - the same 'indistinguishable from linking nothing' conclusion, from the
state the checkout is really in. Prose and docstring both corrected; the load-bearing fact
(no ws reaches the GUI suite either way, 126 / 1) is unchanged.

Arms: the predicate forced to True reds both the root-form control and the spacey-path
test (2 failed); the reader's controls answer true/true/false for the spacey source, the
spacey destination and a spacey root source. Full suite on this branch: 3510 passed, 23
skipped.

---------

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants