design-b D3 prep: pin fiber-on-context nesting and the foreign-stack refusal

Two scenarios, deliberately separate because they answer different
questions that D3 depends on:

- fiber_nests_in_context: can a libcontext-style fiber run nested inside
  a scheduler context at all? YES. Worth pinning explicitly — an earlier
  version of this scenario trapped `unreachable` and briefly looked like
  proof that nesting was impossible, which would have been a serious
  wrong turn for the plan. The real cause was the test's own bug: the
  host-side emscripten_fiber_t was zero-initialised, so the swap unwound
  into a null asyncify buffer. Nesting works; the scenario now proves it.
- foreign_stack_refused: a yield from that nested fiber is REFUSED, and
  the host context and scheduler both survive it.

The spec asserts foreignStackRefusals == 1, so the refusal path cannot
silently stop being exercised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEHGiiXMShNXbBr7gSJ7iz
This commit is contained in:
Gergő Törcsvári 2026-08-06 11:31:39 +02:00
commit 6e80827cec
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
2 changed files with 136 additions and 4 deletions

View file

@ -22,6 +22,8 @@ const SCENARIOS = [
'one_transition_in_flight',
'registry_refusals',
'deep_park_sizing',
'fiber_nests_in_context',
'foreign_stack_refused',
'async_wake',
];
@ -43,6 +45,7 @@ type Stats = {
finished: number;
transitions: number;
refusals: number;
foreignStackRefusals: number;
running: number;
transitionInFlight: boolean;
readyQueued: number;
@ -141,6 +144,16 @@ test.describe('Design B D1 — scheduler contexts', () => {
// unknown id). Zero would mean those scenarios stopped provoking.
expect(stats.refusals, 'illegal operations were refused and counted').toBeGreaterThan(0);
// --- the D3 blocker, pinned -------------------------------------------
// A wait called from a fiber running ON TOP of a context (a KiCad tool
// coroutine opening a dialog) must be refused, not allowed to yield
// someone else's context — that would save the tool fiber's stack into the
// host context's fiber struct. Exactly one scenario provokes it.
expect(
stats.foreignStackRefusals,
'a yield from a foreign stack was refused rather than corrupting the host context',
).toBe(1);
// --- sizing evidence (doc 20 risk 1: derive, don't inherit) ------------
// The high-water mark is what a future buffer size must be justified by.
// Assert it is both real (>0 — the parks did save frames) and comfortably