@zgeoff/atc 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/package.json +1 -1
  2. package/src/agents/agent-adapter.ts +4 -0
  3. package/src/agents/build-claude-query-options.ts +1 -0
  4. package/src/agents/build-headless-env.ts +4 -0
  5. package/src/agents/claude-adapter.ts +4 -3
  6. package/src/agents/gateway-adapter.ts +2 -2
  7. package/src/agents/plan-claude-line-input.ts +44 -0
  8. package/src/client/daemon-client.ts +6 -1
  9. package/src/collect-restart-env.ts +6 -3
  10. package/src/daemon/build-scoped-context.ts +2 -0
  11. package/src/daemon/check-session-scope.ts +235 -0
  12. package/src/daemon/daemon-connection.ts +74 -0
  13. package/src/daemon/daemon-context.ts +21 -0
  14. package/src/daemon/daemon.ts +21 -0
  15. package/src/daemon/find-host-branch.ts +28 -0
  16. package/src/daemon/materialize-workspace.ts +6 -1
  17. package/src/daemon/merge-recorded-scope.ts +50 -0
  18. package/src/daemon/published-records.ts +269 -0
  19. package/src/daemon/request-access-classes.ts +1 -0
  20. package/src/daemon/run-host-git.ts +52 -0
  21. package/src/daemon/sessions.ts +185 -53
  22. package/src/daemon/start-headless-turn.ts +3 -0
  23. package/src/federation/id-rules.ts +11 -2
  24. package/src/mcp/mcp-tools.ts +51 -1
  25. package/src/mcp/require-daemon-features.ts +1 -0
  26. package/src/mcp/run-tool.ts +14 -0
  27. package/src/protocol/daemon-features.ts +5 -0
  28. package/src/protocol/parse-declared-scope.ts +158 -0
  29. package/src/protocol/protocol.ts +1 -0
  30. package/src/protocol/published-record.ts +62 -0
  31. package/src/protocol/request-param-schemas.ts +16 -0
  32. package/src/store/parse-published-record.ts +59 -0
  33. package/src/store/run-migrations.ts +23 -0
  34. package/src/store/state-store.ts +32 -0
  35. package/src/test-utils/build-mock-published-record.ts +39 -0
  36. package/src/test-utils/build-stub-brokered-agent-adapter.ts +3 -1
  37. package/src/test-utils/build-stub-daemon-context.ts +2 -0
  38. package/src/test-utils/build-stub-daemon-requests.ts +3 -3
  39. package/src/test-utils/build-stub-harness-relay.ts +2 -2
  40. package/src/test-utils/build-stub-held-gh.ts +5 -7
  41. package/src/test-utils/build-stub-held-inbox-source.ts +2 -2
  42. package/src/test-utils/build-stub-held-zoxide.ts +4 -6
  43. package/src/test-utils/build-stub-host-hold.ts +7 -6
  44. package/src/test-utils/build-stub-launch-admission.ts +20 -0
  45. package/src/test-utils/build-stub-pregranted-broker-host.ts +22 -0
  46. package/src/test-utils/build-stub-terminal.ts +3 -3
  47. package/src/test-utils/create-git-fixture.ts +2 -2
  48. package/src/test-utils/create-stub-claude.ts +3 -1
  49. package/src/test-utils/create-stub-imp-port.ts +46 -37
  50. package/src/test-utils/create-stub-systemd.ts +6 -6
  51. package/src/test-utils/isolate-home.ts +8 -5
  52. package/src/test-utils/read-query-plan.ts +10 -6
  53. package/src/test-utils/register-test-cleanup.ts +10 -10
  54. package/src/test-utils/{run-legacy-daemon.ts → run-stub-legacy-daemon.ts} +1 -1
  55. package/src/test-utils/setup-mcp-home.ts +7 -6
  56. package/src/test-utils/setup-mcp-http.ts +6 -6
  57. package/src/test-utils/setup-temp-dir.ts +4 -10
  58. package/src/test-utils/start-cut-proxy.ts +2 -4
  59. package/src/test-utils/start-daemon-process.ts +13 -13
  60. package/src/test-utils/start-git-http-server.ts +2 -4
  61. package/src/test-utils/start-mcp-stdio.ts +5 -5
  62. package/src/test-utils/start-stub-answering-listener.ts +3 -3
  63. package/src/test-utils/start-stub-closing-listener.ts +3 -3
  64. package/src/test-utils/start-stub-dropping-daemon.ts +2 -3
  65. package/src/test-utils/start-stub-impd-info.ts +3 -7
  66. package/src/test-utils/start-stub-impd.ts +3 -5
  67. package/src/test-utils/start-stub-legacy-daemon.ts +2 -3
  68. package/src/test-utils/start-stub-mcp-server.ts +3 -5
  69. package/src/test-utils/start-stub-recording-listener.ts +3 -3
  70. package/src/test-utils/start-stub-refusing-listener.ts +3 -3
  71. package/src/test-utils/start-stub-reporter-socket.ts +3 -9
  72. package/src/test-utils/start-stub-restore-daemon.ts +2 -7
  73. package/src/test-utils/start-stub-session-bridge.ts +3 -9
  74. package/src/test-utils/start-stub-silent-server.ts +3 -6
  75. package/src/test-utils/start-stub-source-daemon.ts +3 -3
  76. package/src/test-utils/start-stub-stalled-client.ts +3 -3
  77. package/src/test-utils/start-stub-stalled-listener.ts +3 -3
  78. package/src/test-utils/start-stub-tcp-daemon.ts +3 -3
  79. package/src/test-utils/start-stub-telemetry-collector.ts +2 -2
  80. package/src/test-utils/start-stub-unanswering-listener.ts +3 -3
  81. package/src/test-utils/start-stub-unanswering-unix-listener.ts +3 -3
  82. package/src/test-utils/start-test-daemon.ts +62 -18
  83. package/src/test-utils/start-tui-harness.ts +17 -10
  84. package/src/test-utils/subscribe-to-socket-lines.ts +5 -9
  85. package/src/test-utils/{can-bind-addresses.ts → try-bind-addresses.ts} +4 -3
  86. /package/src/test-utils/{run-source-daemon.ts → run-stub-source-daemon.ts} +0 -0
@@ -39,12 +39,12 @@ interface DaemonBoot {
39
39
  * sending no handshake, and fails at once with the daemon's stderr when the
40
40
  * daemon exits before it listens. `restart` stops the daemon with the given
41
41
  * signal, waits for it to exit, and starts another on the same home with the
42
- * same config. `readStderr` reads what the current boot printed. Disposal
42
+ * same config. `readStderr` reads what the current boot printed. `stop`
43
43
  * closes every client it opened, kills the daemon and waits for it to exit,
44
44
  * then kills the daemon the home's state directory records, which a restart
45
- * the test asked atc for may have started. That disposal runs once the
46
- * current test finishes, so it must run inside a test; disposing sooner
47
- * runs it then, and a second disposal does nothing.
45
+ * the test asked atc for may have started. That stop runs once the current
46
+ * test finishes, so it must run inside a test; calling `stop` sooner runs it
47
+ * then, and a second stop does nothing.
48
48
  */
49
49
  export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
50
50
  const socketPath = join(config.home, 'atc-daemon.sock');
@@ -53,7 +53,7 @@ export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
53
53
  const clients = new Set<DaemonClient>();
54
54
 
55
55
  let boots = 0;
56
- let disposed = false;
56
+ let stopped = false;
57
57
 
58
58
  const boot = (): DaemonBoot => {
59
59
  boots++;
@@ -79,8 +79,8 @@ export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
79
79
 
80
80
  let current = boot();
81
81
 
82
- const dispose = registerTestCleanup(async () => {
83
- disposed = true;
82
+ const stop = registerTestCleanup(async () => {
83
+ stopped = true;
84
84
 
85
85
  for (const client of clients) {
86
86
  client.stop();
@@ -107,12 +107,12 @@ export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
107
107
  }
108
108
  };
109
109
 
110
- // A client that opens after the wait for it ended, or after disposal,
110
+ // A client that opens after the wait for it ended, or after the stop,
111
111
  // closes at once, so nothing outlives the daemon it reached.
112
112
  const openTrackedClient = async (isAbandoned: () => boolean) => {
113
113
  const client = await DaemonClient.open(socketPath);
114
114
 
115
- if (disposed || isAbandoned()) {
115
+ if (stopped || isAbandoned()) {
116
116
  client.stop();
117
117
  throw new Error('the wait for the daemon ended before this client opened');
118
118
  }
@@ -132,7 +132,7 @@ export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
132
132
  const openAfterExit = async () => {
133
133
  await watched.exited;
134
134
 
135
- if (settled || disposed) {
135
+ if (settled || stopped) {
136
136
  return null;
137
137
  }
138
138
 
@@ -146,10 +146,10 @@ export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
146
146
  };
147
147
 
148
148
  // Polls the socket while the daemon runs. Once the other wait settles or
149
- // the daemon is disposed, each attempt resolves empty, which ends the
149
+ // the daemon stops, each attempt resolves empty, which ends the
150
150
  // polling.
151
151
  const openWhenListening = () =>
152
- waitFor(() => (settled || disposed ? null : openTrackedClient(isSettled)), {
152
+ waitFor(() => (settled || stopped ? null : openTrackedClient(isSettled)), {
153
153
  timeoutMs: 15_000,
154
154
  intervalMs: 50,
155
155
  });
@@ -188,6 +188,6 @@ export function startDaemonProcess(config: Readonly<DaemonProcessConfig>) {
188
188
 
189
189
  current = boot();
190
190
  },
191
- [Symbol.asyncDispose]: dispose,
191
+ stop,
192
192
  };
193
193
  }
@@ -7,7 +7,6 @@ interface GitHTTPServer {
7
7
  // Every Authorization header a request carried, in arrival order.
8
8
  readonly authorizations: string[];
9
9
  readonly stop: () => Promise<void>;
10
- readonly [Symbol.asyncDispose]: () => Promise<void>;
11
10
  }
12
11
 
13
12
  interface GitHTTPServerOptions {
@@ -30,8 +29,8 @@ interface GitHTTPServerOptions {
30
29
  * clone through it slow. An `onRequest` callback runs, and is awaited, while
31
30
  * each authenticated request is held, so it sees the client that sent the
32
31
  * request still running. It stops once the current test finishes, so it
33
- * must run inside a test; `stop` or disposal stops it sooner, and a second
34
- * stop does nothing.
32
+ * must run inside a test; `stop` stops it sooner, and a second stop does
33
+ * nothing.
35
34
  */
36
35
  export function startGitHTTPServer(
37
36
  root: string,
@@ -97,7 +96,6 @@ export function startGitHTTPServer(
97
96
  url: `http://127.0.0.1:${server.port}/`,
98
97
  authorizations,
99
98
  stop,
100
- [Symbol.asyncDispose]: stop,
101
99
  };
102
100
  }
103
101
 
@@ -39,8 +39,8 @@ interface MCPToolResult {
39
39
  * with the new session's id. A request still unanswered when the server's
40
40
  * stdout ends rejects. When `initialize` fails, the server is stopped
41
41
  * before the start rejects. The server stops once the current test
42
- * finishes, so it must run inside a test; disposal stops it sooner and waits
43
- * for it to exit, and a second disposal waits for the same exit. The daemon
42
+ * finishes, so it must run inside a test; `stop` stops it sooner and waits
43
+ * for it to exit, and a second stop waits for the same exit. The daemon
44
44
  * stays up for the home to stop.
45
45
  */
46
46
  export async function startMCPStdio(options: MCPStdioOptions) {
@@ -58,7 +58,7 @@ export async function startMCPStdio(options: MCPStdioOptions) {
58
58
  stderr: 'ignore',
59
59
  });
60
60
 
61
- const stopServer = registerTestCleanup(async (): Promise<void> => {
61
+ const stop = registerTestCleanup(async (): Promise<void> => {
62
62
  void proc.stdin.end();
63
63
  proc.kill();
64
64
 
@@ -113,7 +113,7 @@ export async function startMCPStdio(options: MCPStdioOptions) {
113
113
 
114
114
  return id;
115
115
  },
116
- [Symbol.asyncDispose]: stopServer,
116
+ stop,
117
117
  };
118
118
 
119
119
  // A failed initialize stops the server here, before the start rejects.
@@ -122,7 +122,7 @@ export async function startMCPStdio(options: MCPStdioOptions) {
122
122
  capabilities: {},
123
123
  clientInfo: { name: 'atc-test' },
124
124
  }).catch(async (error: unknown) => {
125
- await stopServer();
125
+ await stop();
126
126
 
127
127
  throw error;
128
128
  });
@@ -8,7 +8,7 @@ interface StubAnsweringListener {
8
8
 
9
9
  // The first read each connection sent, decoded, in arrival order.
10
10
  readonly lines: readonly string[];
11
- readonly [Symbol.asyncDispose]: () => Promise<void>;
11
+ readonly stop: () => Promise<void>;
12
12
  }
13
13
 
14
14
  /**
@@ -17,7 +17,7 @@ interface StubAnsweringListener {
17
17
  * read each one sends, answers that read with the line `answer`, and
18
18
  * leaves every later byte unread on the connection. Resolves once it
19
19
  * listens. Once the current test finishes, it destroys every connection it
20
- * accepted and closes, so it must run inside a test; disposal does so
20
+ * accepted and closes, so it must run inside a test; `stop` does so
21
21
  * sooner and resolves once the server has closed, and a second stop does
22
22
  * nothing.
23
23
  */
@@ -60,6 +60,6 @@ export async function startStubAnsweringListener(path: string): Promise<StubAnsw
60
60
  return {
61
61
  peers,
62
62
  lines,
63
- [Symbol.asyncDispose]: stop,
63
+ stop,
64
64
  };
65
65
  }
@@ -1,13 +1,13 @@
1
1
  import { registerTestCleanup } from './register-test-cleanup';
2
2
 
3
3
  interface StubClosingListener {
4
- readonly [Symbol.dispose]: () => void;
4
+ readonly stop: () => void;
5
5
  }
6
6
 
7
7
  /**
8
8
  * A stand-in for a daemon that hangs up: it listens on the unix socket path
9
9
  * and ends every connection as soon as it opens, reading nothing. It stops
10
- * once the current test finishes, so it must run inside a test; disposal
10
+ * once the current test finishes, so it must run inside a test; `stop`
11
11
  * stops it sooner, and a second stop does nothing.
12
12
  */
13
13
  export function startStubClosingListener(path: string): StubClosingListener {
@@ -26,6 +26,6 @@ export function startStubClosingListener(path: string): StubClosingListener {
26
26
  });
27
27
 
28
28
  return {
29
- [Symbol.dispose]: stop,
29
+ stop,
30
30
  };
31
31
  }
@@ -25,8 +25,8 @@ interface DroppingDaemonOptions {
25
25
  * idempotency key each of those requests carried, `undefined` for one that
26
26
  * carried none. `reads` counts the reads it has taken from every connection,
27
27
  * so a test can wait until one piece of a split write has arrived. It stops
28
- * once the current test finishes, so it must run inside a test; `stop` or
29
- * disposal stops it sooner, and a second stop does nothing.
28
+ * once the current test finishes, so it must run inside a test; `stop`
29
+ * stops it sooner, and a second stop does nothing.
30
30
  */
31
31
  export function startStubDroppingDaemon(socketPath: string, options: DroppingDaemonOptions) {
32
32
  const keys: unknown[] = [];
@@ -100,6 +100,5 @@ export function startStubDroppingDaemon(socketPath: string, options: DroppingDae
100
100
  return reads;
101
101
  },
102
102
  stop,
103
- [Symbol.dispose]: stop,
104
103
  };
105
104
  }
@@ -6,9 +6,8 @@ import { registerTestCleanup } from './register-test-cleanup';
6
6
  * `{ json: info }`, so a client reads `info.features` as impd's features;
7
7
  * the test sets `info.features` to what impd should report. `paths` holds
8
8
  * the request path of each call, in order. The server stops once the
9
- * current test finishes, so it must run inside a test; disposal stops it
10
- * sooner, the asynchronous form resolving once it has stopped, and a second
11
- * stop does nothing.
9
+ * current test finishes, so it must run inside a test; `stop` stops it
10
+ * sooner and resolves once it has stopped, and a second stop does nothing.
12
11
  */
13
12
  export function startStubImpdInfo() {
14
13
  const paths: string[] = [];
@@ -30,9 +29,6 @@ export function startStubImpdInfo() {
30
29
  url: `http://127.0.0.1:${String(server.port)}`,
31
30
  paths,
32
31
  info,
33
- [Symbol.dispose]: () => {
34
- void stop();
35
- },
36
- [Symbol.asyncDispose]: stop,
32
+ stop,
37
33
  };
38
34
  }
@@ -18,8 +18,8 @@ type StubExecReply = 'refuse' | 'count' | 'exit-early';
18
18
  * whose broker is not ready by default. It takes no WebSocket message over
19
19
  * 2 MiB, as impd refuses one over its own limit. The server stops, dropping
20
20
  * every open connection at once, when the current test finishes, so it must
21
- * run inside a test; disposal stops it sooner, and a second stop does
22
- * nothing.
21
+ * run inside a test; `stop` stops it sooner and resolves once it has
22
+ * stopped, and a second stop does nothing.
23
23
  */
24
24
  export function startStubImpd() {
25
25
  const authorizations: (string | null)[] = [];
@@ -136,8 +136,6 @@ export function startStubImpd() {
136
136
  controls,
137
137
  execOpens,
138
138
  exec,
139
- [Symbol.dispose]: () => {
140
- void stop();
141
- },
139
+ stop,
142
140
  };
143
141
  }
@@ -47,8 +47,8 @@ interface StubLegacyDaemonOptions {
47
47
  * `connections` counts the connections it accepted and those still open.
48
48
  * `port` holds the TCP port it bound, `null` on a unix socket. It stops
49
49
  * once the current test finishes, so it must run inside a test, unless the
50
- * options make the caller its owner; `stop` or disposal stops it sooner, and
51
- * a second stop does nothing.
50
+ * options make the caller its owner; `stop` stops it sooner, and a second
51
+ * stop does nothing.
52
52
  */
53
53
  export function startStubLegacyDaemon(
54
54
  address: string | StubLegacyDaemonTCPAddress,
@@ -135,7 +135,6 @@ export function startStubLegacyDaemon(
135
135
  connections,
136
136
  port: server.port,
137
137
  stop,
138
- [Symbol.dispose]: stop,
139
138
  };
140
139
  }
141
140
 
@@ -14,7 +14,7 @@ interface StubMCPServer {
14
14
  // Every request the stand-in received, in arrival order.
15
15
  readonly requests: readonly StubMCPRequest[];
16
16
 
17
- readonly [Symbol.dispose]: () => void;
17
+ readonly stop: () => Promise<void>;
18
18
  }
19
19
 
20
20
  /**
@@ -22,7 +22,7 @@ interface StubMCPServer {
22
22
  * request with the JSON body given, and records each request's method,
23
23
  * path, authorization and content-type headers, and JSON body. `url` holds
24
24
  * its origin. It stops once the current test finishes, so it must run
25
- * inside a test; disposal stops it sooner, and a second stop does nothing.
25
+ * inside a test; `stop` stops it sooner, and a second stop does nothing.
26
26
  */
27
27
  export function startStubMCPServer(answer: unknown): StubMCPServer {
28
28
  const requests: StubMCPRequest[] = [];
@@ -48,8 +48,6 @@ export function startStubMCPServer(answer: unknown): StubMCPServer {
48
48
  return {
49
49
  url: `http://127.0.0.1:${String(server.port)}`,
50
50
  requests,
51
- [Symbol.dispose]: () => {
52
- void stop();
53
- },
51
+ stop,
54
52
  };
55
53
  }
@@ -7,7 +7,7 @@ interface StubRecordingListener {
7
7
 
8
8
  // What each read took from any connection, decoded, in arrival order.
9
9
  readonly received: readonly string[];
10
- readonly [Symbol.dispose]: () => void;
10
+ readonly stop: () => void;
11
11
  }
12
12
 
13
13
  /**
@@ -15,7 +15,7 @@ interface StubRecordingListener {
15
15
  * socket path, hands the test the server's side of the first connection it
16
16
  * accepts, records what each read takes from any connection, and sends
17
17
  * nothing of its own. It stops, dropping every connection it holds, once
18
- * the current test finishes, so it must run inside a test; disposal stops it
18
+ * the current test finishes, so it must run inside a test; `stop` stops it
19
19
  * sooner, and a second stop does nothing.
20
20
  */
21
21
  export function startStubRecordingListener(path: string): StubRecordingListener {
@@ -42,6 +42,6 @@ export function startStubRecordingListener(path: string): StubRecordingListener
42
42
  return {
43
43
  accepted: accepted.promise,
44
44
  received,
45
- [Symbol.dispose]: stop,
45
+ stop,
46
46
  };
47
47
  }
@@ -6,7 +6,7 @@ interface StubRefusingListener {
6
6
  // What each read took from any connection, decoded, in arrival order.
7
7
  readonly received: readonly string[];
8
8
 
9
- readonly [Symbol.dispose]: () => void;
9
+ readonly stop: () => void;
10
10
  }
11
11
 
12
12
  /**
@@ -14,7 +14,7 @@ interface StubRefusingListener {
14
14
  * line is not a handshake: on a loopback port, it records what each read
15
15
  * takes from a connection and ends that connection, answering nothing.
16
16
  * It stops once the current test finishes, so it must run inside a test;
17
- * disposal stops it sooner, and a second stop does nothing.
17
+ * `stop` stops it sooner, and a second stop does nothing.
18
18
  */
19
19
  export function startStubRefusingListener(): StubRefusingListener {
20
20
  const received: string[] = [];
@@ -38,6 +38,6 @@ export function startStubRefusingListener(): StubRefusingListener {
38
38
  return {
39
39
  port: server.port,
40
40
  received,
41
- [Symbol.dispose]: stop,
41
+ stop,
42
42
  };
43
43
  }
@@ -9,8 +9,7 @@ interface StubReporterSocket {
9
9
  // can wait until one piece of a split write has arrived.
10
10
  readonly reads: number;
11
11
  readonly waitForLine: (timeoutMs?: number) => Promise<string>;
12
- readonly [Symbol.dispose]: () => void;
13
- readonly [Symbol.asyncDispose]: () => Promise<void>;
12
+ readonly stop: () => void;
14
13
  }
15
14
 
16
15
  /**
@@ -21,7 +20,7 @@ interface StubReporterSocket {
21
20
  * connection for the sender to close. `waitForLine` resolves with the first
22
21
  * line once one has arrived, and rejects naming the socket when none arrives
23
22
  * within `timeoutMs`, 5 seconds by default. The listener stops once the
24
- * current test finishes, so it must run inside a test; disposal stops it
23
+ * current test finishes, so it must run inside a test; `stop` stops it
25
24
  * sooner, and a second stop does nothing.
26
25
  */
27
26
  export function startStubReporterSocket(path: string): StubReporterSocket {
@@ -69,11 +68,6 @@ export function startStubReporterSocket(path: string): StubReporterSocket {
69
68
  },
70
69
  { timeoutMs },
71
70
  ),
72
- [Symbol.dispose]: stop,
73
- [Symbol.asyncDispose]: () => {
74
- stop();
75
-
76
- return Promise.resolve();
77
- },
71
+ stop,
78
72
  };
79
73
  }
@@ -9,7 +9,7 @@ import { registerTestCleanup } from './register-test-cleanup';
9
9
  * onto `lists`, and withholds the answer to any request that finds `lists`
10
10
  * empty. `methods` holds the method of each request it took, in order.
11
11
  * The listener stops once the current test finishes, so it must run inside
12
- * a test; disposal stops it sooner, and a second stop does nothing.
12
+ * a test; `stop` stops it sooner, and a second stop does nothing.
13
13
  */
14
14
  export function startStubRestoreDaemon(socketPath: string) {
15
15
  const lists: Readonly<Record<string, unknown>>[] = [];
@@ -49,11 +49,6 @@ export function startStubRestoreDaemon(socketPath: string) {
49
49
  return {
50
50
  lists,
51
51
  methods,
52
- [Symbol.dispose]: stop,
53
- [Symbol.asyncDispose]: () => {
54
- stop();
55
-
56
- return Promise.resolve();
57
- },
52
+ stop,
58
53
  };
59
54
  }
@@ -16,8 +16,7 @@ interface StubSessionBridge {
16
16
  // How many reads the stand-in has taken from its connections, so a test
17
17
  // can wait until one piece of a split write has arrived.
18
18
  readonly reads: number;
19
- readonly [Symbol.dispose]: () => void;
20
- readonly [Symbol.asyncDispose]: () => Promise<void>;
19
+ readonly stop: () => void;
21
20
  }
22
21
 
23
22
  /**
@@ -28,7 +27,7 @@ interface StubSessionBridge {
28
27
  * does once it attaches the tap, and hands every other request to the
29
28
  * responder, writing back each line it returns. A line that is not a JSON
30
29
  * object reaches the responder as an empty request. The listener stops once
31
- * the current test finishes, so it must run inside a test; disposal stops it
30
+ * the current test finishes, so it must run inside a test; `stop` stops it
32
31
  * sooner, and a second stop does nothing.
33
32
  */
34
33
  export function startStubSessionBridge(path: string, respond: BridgeResponder): StubSessionBridge {
@@ -80,12 +79,7 @@ export function startStubSessionBridge(path: string, respond: BridgeResponder):
80
79
  get reads() {
81
80
  return reads;
82
81
  },
83
- [Symbol.dispose]: stop,
84
- [Symbol.asyncDispose]: () => {
85
- stop();
86
-
87
- return Promise.resolve();
88
- },
82
+ stop,
89
83
  };
90
84
  }
91
85
 
@@ -5,8 +5,8 @@ import { registerTestCleanup } from './register-test-cleanup';
5
5
  * the loopback address, takes every request, and never answers one. `paths`
6
6
  * holds the request path of each request it took, in order. The server
7
7
  * stops, dropping the connections it holds, once the current test finishes,
8
- * so it must run inside a test; disposal stops it sooner, the asynchronous
9
- * form resolving once it has stopped, and a second stop does nothing.
8
+ * so it must run inside a test; `stop` stops it sooner and resolves once it
9
+ * has stopped, and a second stop does nothing.
10
10
  */
11
11
  export function startStubSilentServer() {
12
12
  const paths: string[] = [];
@@ -30,9 +30,6 @@ export function startStubSilentServer() {
30
30
  return {
31
31
  url: `http://127.0.0.1:${String(server.port)}/`,
32
32
  paths,
33
- [Symbol.dispose]: () => {
34
- void stop();
35
- },
36
- [Symbol.asyncDispose]: stop,
33
+ stop,
37
34
  };
38
35
  }
@@ -10,10 +10,10 @@ import { registerTestCleanup } from './register-test-cleanup';
10
10
  * `none`; `ATC_TEST_FIXTURE_URL` holds the repository the fixture source
11
11
  * lists, and `ATC_TEST_SOURCE_LOG` a file it appends each listing to.
12
12
  * The process stops once the current test finishes, so it must run inside
13
- * a test; disposal stops it sooner, and a second stop does nothing.
13
+ * a test; `stop` stops it sooner, and a second stop does nothing.
14
14
  */
15
15
  export async function startStubSourceDaemon(env: Readonly<Record<string, string | undefined>>) {
16
- const daemon = Bun.spawn([process.execPath, join(import.meta.dir, 'run-source-daemon.ts')], {
16
+ const daemon = Bun.spawn([process.execPath, join(import.meta.dir, 'run-stub-source-daemon.ts')], {
17
17
  env: { ATC_TEST_SOURCES: 'fixture', ...env },
18
18
  stdout: 'pipe',
19
19
  stderr: 'ignore',
@@ -37,5 +37,5 @@ export async function startStubSourceDaemon(env: Readonly<Record<string, string
37
37
  throw new Error('the source daemon exited before it listened');
38
38
  }
39
39
 
40
- return { pid: daemon.pid, [Symbol.asyncDispose]: stop };
40
+ return { pid: daemon.pid, stop };
41
41
  }
@@ -8,7 +8,7 @@ interface StubStalledClient {
8
8
 
9
9
  // How many bytes have arrived on the connection and wait unread.
10
10
  readonly countUnreadBytes: () => number;
11
- readonly [Symbol.dispose]: () => void;
11
+ readonly stop: () => void;
12
12
  }
13
13
 
14
14
  /**
@@ -18,7 +18,7 @@ interface StubStalledClient {
18
18
  * a promise that never resolves, so whatever the daemon sends it after
19
19
  * that backs up. Resolves once that first chunk arrives. The connection is
20
20
  * destroyed once the current test finishes, so it must run inside a test;
21
- * disposal destroys it sooner, and a second stop does nothing.
21
+ * `stop` destroys it sooner, and a second stop does nothing.
22
22
  */
23
23
  export async function startStubStalledClient(
24
24
  socketPath: string,
@@ -56,6 +56,6 @@ export async function startStubStalledClient(
56
56
  return {
57
57
  chunks,
58
58
  countUnreadBytes: () => socket.readableLength,
59
- [Symbol.dispose]: stop,
59
+ stop,
60
60
  };
61
61
  }
@@ -3,7 +3,7 @@ import type { Socket } from 'node:net';
3
3
  import { registerTestCleanup } from './register-test-cleanup';
4
4
 
5
5
  interface StubStalledListener {
6
- readonly [Symbol.dispose]: () => void;
6
+ readonly stop: () => void;
7
7
  }
8
8
 
9
9
  /**
@@ -11,7 +11,7 @@ interface StubStalledListener {
11
11
  * path and accepts every connection but never reads from it, so what the
12
12
  * kernel does not buffer stays queued on the sender's side. Resolves once
13
13
  * it listens. Once the current test finishes, it destroys every connection
14
- * it accepted and stops, so it must run inside a test; disposal does so
14
+ * it accepted and stops, so it must run inside a test; `stop` does so
15
15
  * sooner, and a second stop does nothing.
16
16
  */
17
17
  export async function startStubStalledListener(path: string): Promise<StubStalledListener> {
@@ -39,6 +39,6 @@ export async function startStubStalledListener(path: string): Promise<StubStalle
39
39
  });
40
40
 
41
41
  return {
42
- [Symbol.dispose]: stop,
42
+ stop,
43
43
  };
44
44
  }
@@ -12,7 +12,7 @@ interface StubTCPDaemon {
12
12
  // can wait until one piece of a split write has arrived before it sends
13
13
  // the next.
14
14
  readonly reads: number;
15
- readonly [Symbol.dispose]: () => void;
15
+ readonly stop: () => void;
16
16
  }
17
17
 
18
18
  // The two request fields the stand-in reads; every other field passes
@@ -25,7 +25,7 @@ const REQUEST = z.object({ id: z.number(), m: z.string() });
25
25
  * the request's method, recording every method in `seen`. It sends no
26
26
  * handshake of its own and checks no token. `reads` counts the reads it has
27
27
  * taken from every connection. It stops once the current test finishes, so
28
- * it must run inside a test; disposal stops it sooner, and a second stop
28
+ * it must run inside a test; `stop` stops it sooner, and a second stop
29
29
  * does nothing.
30
30
  */
31
31
  export function startStubTCPDaemon(): StubTCPDaemon {
@@ -70,6 +70,6 @@ export function startStubTCPDaemon(): StubTCPDaemon {
70
70
  get reads() {
71
71
  return reads;
72
72
  },
73
- [Symbol.dispose]: stop,
73
+ stop,
74
74
  };
75
75
  }
@@ -5,7 +5,7 @@ import { registerTestCleanup } from './register-test-cleanup';
5
5
  * loopback address. A process whose `BETTER_AUTH_TELEMETRY_ENDPOINT` holds
6
6
  * `url` sends its telemetry here. Every request is answered with HTTP 204,
7
7
  * and `received` holds the URL of each request, in order. The server stops
8
- * once the current test finishes, so it must run inside a test; disposal
8
+ * once the current test finishes, so it must run inside a test; `stop`
9
9
  * stops it sooner and resolves once it has stopped, and a second stop does
10
10
  * nothing.
11
11
  */
@@ -27,6 +27,6 @@ export function startStubTelemetryCollector() {
27
27
  return {
28
28
  url: `http://127.0.0.1:${String(server.port)}/`,
29
29
  received,
30
- [Symbol.asyncDispose]: stop,
30
+ stop,
31
31
  };
32
32
  }
@@ -6,7 +6,7 @@ interface StubUnansweringListener {
6
6
  // What each read took from any connection, decoded, in arrival order.
7
7
  readonly received: readonly string[];
8
8
 
9
- readonly [Symbol.dispose]: () => void;
9
+ readonly stop: () => void;
10
10
  }
11
11
 
12
12
  /**
@@ -14,7 +14,7 @@ interface StubUnansweringListener {
14
14
  * on a loopback port, it records what each read takes from a connection,
15
15
  * sends nothing back, and keeps the connection open. It stops, dropping
16
16
  * every connection it holds, once the current test finishes, so it must run
17
- * inside a test; disposal stops it sooner, and a second stop does nothing.
17
+ * inside a test; `stop` stops it sooner, and a second stop does nothing.
18
18
  */
19
19
  export function startStubUnansweringListener(): StubUnansweringListener {
20
20
  const received: string[] = [];
@@ -36,6 +36,6 @@ export function startStubUnansweringListener(): StubUnansweringListener {
36
36
  return {
37
37
  port: server.port,
38
38
  received,
39
- [Symbol.dispose]: stop,
39
+ stop,
40
40
  };
41
41
  }
@@ -4,7 +4,7 @@ interface StubUnansweringUnixListener {
4
4
  // What each read took from any connection, decoded, in arrival order.
5
5
  readonly received: readonly string[];
6
6
 
7
- readonly [Symbol.dispose]: () => void;
7
+ readonly stop: () => void;
8
8
  }
9
9
 
10
10
  /**
@@ -12,7 +12,7 @@ interface StubUnansweringUnixListener {
12
12
  * it: it listens on the unix socket path, records what each read takes from
13
13
  * a connection, sends nothing back, and keeps the connection open. It
14
14
  * stops, dropping every connection it holds, once the current test
15
- * finishes, so it must run inside a test; disposal stops it sooner, and a
15
+ * finishes, so it must run inside a test; `stop` stops it sooner, and a
16
16
  * second stop does nothing.
17
17
  */
18
18
  export function startStubUnansweringUnixListener(path: string): StubUnansweringUnixListener {
@@ -33,6 +33,6 @@ export function startStubUnansweringUnixListener(path: string): StubUnansweringU
33
33
 
34
34
  return {
35
35
  received,
36
- [Symbol.dispose]: stop,
36
+ stop,
37
37
  };
38
38
  }