✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (125.098541ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (134.665935ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (96.766909ms)
✔ decide prints a declining choice as declining (108.478466ms)
✔ an unknown outcome is reported once and never resent (83.652622ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (85.260087ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (85.215231ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (47.360022ms)
✔ every human command refuses inside an agent run before it touches the bus (74.792694ms)
✔ usage errors exit 4; no business and no host is a usage error (67.921609ms)
✔ agents and tasks print through the broker (75.297097ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.397368ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (42.16644ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (32.376277ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (32.360128ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (28.459949ms)
✔ a business without tracker.baseUrl gets no trackers entry (27.435579ms)
✔ an unknown business and a broken system config refuse with exit 3 (52.425052ms)
✔ empty views say so (1.054835ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.31622ms)
✔ tasks print the tracker fields the snapshot carries (0.230229ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (900.777403ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (197.314943ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (106.753089ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1123.524195ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (234.234287ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (113.460887ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (156.637981ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (147.392036ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (113.446985ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (147.381832ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (94.058726ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (159.970362ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (24.968625ms)
✔ bus stop refuses to signal a live pid that is not a bus host (202.36153ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (228.950236ms)
✔ bus start refuses with exit 3 without a notifier config (97.197829ms)
✔ bus start runs until bus stop; status reports it while it runs (656.505643ms)
✔ bus-service.sh renders the unit and installs it into a given directory (26.52731ms)
✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (30264.958209ms)
✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (18485.409173ms)
✔ a runner that stops at once ends its launch with the runner's reason (194.401086ms)
✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (30544.742537ms)
✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (33061.504326ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (105.073482ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (108.473075ms)
✔ a launch client that never closes its side doesn't hold the host's close (122.229839ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1224.323487ms)
✔ zoned uses the IANA zone across DST (15.998202ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (106.49839ms)
✔ two blocking decisions get two DMs with different nonces (136.681083ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.247401ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (105.173087ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (91.885581ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (88.046523ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (96.766445ms)
✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (133.166052ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (121.365828ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (96.241289ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (68.046994ms)
✔ an inbox read failure is logged and the next poll retries (0.652732ms)
✔ no Discord id reaches the journal or the log (52.636044ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (15.640962ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (15.244681ms)
✔ the journal: a whole file that is one torn line truncates to empty (10.205265ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.5312ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.565166ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.778854ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.310747ms)
✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.451018ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.375168ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.390645ms)
✔ digest content stays within Discord's 2000 characters (0.230013ms)
✔ runLoop never overlaps ticks and stops after the one in flight (111.867714ms)
task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200
task.close on a missing task answered: task-not-found; it made GET /tasks/999 404
✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (378.858886ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (38.33439ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2142.861729ms)
✔ busExit and refuseInsideAgent (0.427438ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (208.921317ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1129.911757ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (45.073004ms)
✔ launches off and on go to the broker and change the business's launch state (54.562449ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (55.056715ms)
ℹ tests 83
ℹ suites 0
ℹ pass 79
ℹ fail 1
ℹ cancelled 3
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 114201.684561

✖ failing tests:

test at packages/cli/tests/launcher.test.mjs:106:1
✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (30264.958209ms)
  'test timed out after 30000ms'

test at packages/cli/tests/launcher.test.mjs:176:1
✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (18485.409173ms)
  Error: timed out waiting
      at until (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:43:9)
      at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:216:3)
      at async Test.run (node:internal/test_runner/test:1409:7)
      at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)

test at packages/cli/tests/launcher.test.mjs:253:3
✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (30544.742537ms)
  'test timed out after 30000ms'

test at packages/cli/tests/launcher.test.mjs:253:3
✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (33061.504326ms)
  'test timed out after 30000ms'
