findings(E-5,E-6,E-7,E-8,E-9): close the test-coverage gaps + kicad ifdef bump

Coverage audit found three fixes without a test that could catch their
reversion:

- E-6 ack-on-enqueue: new unit case "acks a pre-handler queued frame at
  enqueue so the transport window never starves" (ngspice-service.test.ts;
  false-green audited — fails with the ack removed, exactly one ack per
  frame, none repeated on drain).
- E-5 / E-8 wiring / E-9 / E-7 shape: new source-contract tripwire
  tests/tools/findings-e-source-contract.ts (npm run findings-e:contract) —
  the codex-thread contract style for C++/EM_JS code that cannot be
  behaviorally unit-tested without a wasm build. Asserts: the module
  identity stamp + self-disarm and ABSENCE of the install-once presence
  guard (E-5); all four completion sites use runWaitCompletion, no stub
  resolves a wait directly, token<=0 bails, the shim exports the gate
  surface (E-8); the run-generation mechanism present with its behavioral
  drops confined to the wasm build (E-7); the identity-checked destructor
  unregistration (E-9).

Also bumps kicad to the E-7 ifdef-confinement commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Istvan Matejcsok 2026-08-24 15:37:21 +02:00
commit 0f21a33216
4 changed files with 128 additions and 2 deletions

View file

@ -414,6 +414,46 @@ describe("ngspice service worker lifetime", () => {
expect(events).toEqual(["new"]);
});
it("acks a pre-handler queued frame at enqueue so the transport window never starves", async () => {
// Pin for the E-6 refinement: placing a frame in the bounded mirror queue
// IS taking ownership — without the enqueue-time ack, a stream that starts
// before the C++ handler installs pins the worker's 64-frame credit
// window open forever (observed as the ngspice-probe bg_halt starvation).
const first = await readyRequest(0);
expect(globalThis.__ngspiceOnEvent).toBeUndefined();
first.worker.emitMessage({
evt: { kind: "char", lines: ["early"] },
eventSequence: 1,
eventBytes: 33,
});
await vi.waitFor(() =>
expect(first.worker.postMessage).toHaveBeenCalledWith({
eventAck: { sequence: 1, bytes: 33 },
}));
// Once the handler installs, the queued frame is dispatched WITHOUT a
// second ack; only the new frame earns one.
const events: string[] = [];
globalThis.__ngspiceOnEvent = (event) => events.push(event.lines?.[0] ?? event.kind);
first.worker.emitMessage({
evt: { kind: "char", lines: ["late"] },
eventSequence: 2,
eventBytes: 32,
});
await vi.waitFor(() => expect(events).toEqual(["early", "late"]));
const acks = first.worker.postMessage.mock.calls
.map((call) => (call[0] as { eventAck?: { sequence: number; bytes: number } }).eventAck)
.filter(Boolean);
expect(acks, "exactly one ack per frame, none repeated on drain").toEqual([
{ sequence: 1, bytes: 33 },
{ sequence: 2, bytes: 32 },
]);
first.worker.emitMessage({ id: first.id, res: { ret: 0 } });
await expect(first.request).resolves.toEqual({ ret: 0 });
});
it("retires a worker whose bounded event stream reports a fatal line", async () => {
const first = await readyRequest(0, commandRequest("oversize output"));
first.worker.emitMessage({