REPOSITORY SOURCE

relay-shutdown-acceptance.json

docs/evidence/relay-shutdown-acceptance.json

1{
2  "format": 1,
3  "recordedAt": "2026-10-25T00:07:53.655419+00:00",
4  "source": "54aebb89391f12c9883d6164b76b27c751734fcd",
5  "previousCi": {
6    "source": "107e9339dc36921a8a0fdbc6bddf0bd63cd8e254",
7    "url": "https://github.com/jasona/darkwind-web/actions/runs/40145062459",
8    "containerJob": 121024514762,
9    "container": "failure",
10    "core": "success",
11    "browserPlayer": "success",
12    "browserMultiplayer": "in_progress at recording",
13    "browserProbe": "in_progress at recording",
14    "failure": "SIGTERM exits cleanly: expected 0, actual 1, tools/cli/docker-smoke.ts:72",
15    "beforeFailure": "Image built; four production-only model imports, license routes, static assets, admission, two-client shared door and duplicate-writer lock assertion completed.",
16    "finding": "Docker smoke left player/worker asset bodies unread. A separate reproduction confirms a stalled large HTTP response can prevent the old close() from completing until the main-process watchdog exits with code 1."
17  },
18  "reproduction": {
19    "before": {
20      "request": "Paused 32 MiB HTTP asset response against createRelay",
21      "observation": "Still waiting after 3004 ms; client was explicitly destroyed for cleanup. This bounded observation is not a measured completion time."
22    },
23    "after": {
24      "request": "Same paused 32 MiB HTTP asset response",
25      "result": "closed",
26      "elapsedMs": 2001.16561
27    },
28    "production": {
29      "code": 0,
30      "signal": null,
31      "elapsedMs": 2012.3418719999997,
32      "mode": "compiled production server; paused 32 MiB asset response; SIGTERM"
33    },
34    "productionCommand": "npm run build:server && node docs/evidence/relay-shutdown-production.mjs",
35    "productionScript": "relay-shutdown-production.mjs"
36  },
37  "changes": [
38    "Fence new HTTP requests, upgrades, WebSocket messages and unfinished admissions when stopping begins.",
39    "Start HTTP close immediately, terminate room sockets and force remaining transport sockets closed after a two-second drain.",
40    "Close is idempotent; interrupted static streams release ownership.",
41    "Docker smoke consumes full player and render-worker downloads."
42  ],
43  "headless": {
44    "command": "npm test",
45    "tests": 246,
46    "pass": 246,
47    "fail": 0,
48    "skipped": 0,
49    "log": "relay-shutdown-headless.tap",
50    "provenance": "Full suite with animation work parked outside the checkout. Final runtime behavior tested; subsequent change only adds a TypeScript error-code cast."
51  },
52  "browser": {
53    "command": "npm run test:browser -- tests/browser/multiplayer.spec.ts",
54    "stats": {
55      "startTime": "2026-10-25T00:06:11.037Z",
56      "duration": 67148.349,
57      "expected": 4,
58      "skipped": 0,
59      "unexpected": 0,
60      "flaky": 0
61    },
62    "report": "relay-shutdown-browser.json",
63    "environment": "Chromium 141 software WebGPU; CI=1; one worker; isolated temporary production client and room storage",
64    "provenance": "Four real-browser multiplayer scenarios, including shared graphics recovery. The prior complete 26-case foliage run is retained in alpha-materials-acceptance.json."
65  },
66  "validation": {
67    "typeAndImports": "passed, 56 package source files",
68    "productionServerBuild": "passed",
69    "diffWhitespace": "passed",
70    "docker": "Current fix awaits remote Docker acceptance; local daemon unavailable."
71  },
72  "limits": [
73    "Two-second drain is a timer deadline; CPU starvation can delay timer delivery.",
74    "Incomplete HTTP downloads are terminated during shutdown and must be retried.",
75    "Does not establish a fix for the earlier graphics-capture stall; the player CI job passed on the foliage revision without a targeted capture fix.",
76    "No merge or deployment to main. Full engine plan remains open."
77  ]
78}
79