omkit 0.0.14 → 0.1.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 (110) hide show
  1. package/README.md +110 -97
  2. package/dist/actions/chrome-page.d.ts +12 -29
  3. package/dist/actions/chrome-page.js +19 -32
  4. package/dist/actions/command.d.ts +8 -41
  5. package/dist/actions/command.js +15 -82
  6. package/dist/actions/healthcheck.d.ts +3 -11
  7. package/dist/actions/healthcheck.js +8 -22
  8. package/dist/actions/index.d.ts +6 -8
  9. package/dist/actions/index.js +4 -6
  10. package/dist/actions/prompt.d.ts +5 -19
  11. package/dist/actions/prompt.js +10 -31
  12. package/dist/actions/tail-log.d.ts +7 -21
  13. package/dist/actions/tail-log.js +13 -34
  14. package/dist/actions/until-log.d.ts +4 -7
  15. package/dist/actions/until-log.js +3 -3
  16. package/dist/actions/watch-dir.d.ts +5 -19
  17. package/dist/actions/watch-dir.js +13 -28
  18. package/dist/actions/watch.d.ts +4 -12
  19. package/dist/actions/watch.js +9 -16
  20. package/dist/core/ActionRun.d.ts +97 -0
  21. package/dist/core/ActionRun.js +266 -0
  22. package/dist/core/ExecutionTree.d.ts +88 -0
  23. package/dist/core/ExecutionTree.js +320 -0
  24. package/dist/core/action.d.ts +6 -0
  25. package/dist/core/action.js +72 -0
  26. package/dist/core/context.d.ts +9 -0
  27. package/dist/core/context.js +8 -0
  28. package/dist/core/om.d.ts +9 -0
  29. package/dist/core/om.js +25 -0
  30. package/dist/core/step.d.ts +9 -0
  31. package/dist/core/step.js +18 -0
  32. package/dist/core/types.d.ts +128 -0
  33. package/dist/foundation/ActionRef.d.ts +18 -0
  34. package/dist/foundation/ActionRef.js +5 -0
  35. package/dist/{utils → foundation}/AsyncQueue.d.ts +2 -4
  36. package/dist/{utils → foundation}/AsyncQueue.js +6 -19
  37. package/dist/foundation/CancelledError.d.ts +10 -0
  38. package/dist/foundation/CancelledError.js +13 -0
  39. package/dist/foundation/Deferred.d.ts +8 -0
  40. package/dist/foundation/Deferred.js +10 -0
  41. package/dist/foundation/LogEntry.d.ts +29 -0
  42. package/dist/{orchestration/events → foundation}/events.d.ts +2 -11
  43. package/dist/{orchestration/events → foundation}/events.js +1 -7
  44. package/dist/{utils → foundation}/format.d.ts +3 -4
  45. package/dist/{utils → foundation}/format.js +12 -4
  46. package/dist/foundation/fsSafe.d.ts +7 -0
  47. package/dist/foundation/fsSafe.js +7 -0
  48. package/dist/foundation/ids.d.ts +9 -0
  49. package/dist/foundation/ids.js +10 -0
  50. package/dist/index.d.ts +11 -22
  51. package/dist/index.js +10 -30
  52. package/dist/output/LiveRenderer.d.ts +13 -0
  53. package/dist/output/LiveRenderer.js +56 -0
  54. package/dist/output/console/ConsoleCapture.d.ts +20 -0
  55. package/dist/output/console/ConsoleCapture.js +47 -0
  56. package/dist/output/folder/RunFolder.d.ts +11 -22
  57. package/dist/output/folder/RunFolder.js +18 -36
  58. package/dist/output/log/LogStore.d.ts +22 -0
  59. package/dist/output/log/LogStore.js +56 -0
  60. package/dist/output/log/RawStream.d.ts +11 -0
  61. package/dist/output/log/RawStream.js +18 -0
  62. package/dist/output/snapshot/SnapshotStore.d.ts +6 -43
  63. package/dist/output/snapshot/SnapshotStore.js +12 -52
  64. package/dist/output/writers/NodeLogWriter.d.ts +8 -0
  65. package/dist/output/writers/NodeLogWriter.js +36 -0
  66. package/dist/output/writers/ResultWriter.d.ts +3 -0
  67. package/dist/output/writers/ResultWriter.js +5 -0
  68. package/dist/output/writers/RollupWriter.d.ts +9 -0
  69. package/dist/output/writers/RollupWriter.js +29 -0
  70. package/dist/output/writers/entryLine.d.ts +10 -0
  71. package/dist/output/writers/entryLine.js +39 -0
  72. package/dist/output/writers/views.d.ts +38 -0
  73. package/dist/output/writers/views.js +6 -0
  74. package/dist/system/fs/FolderCache.d.ts +6 -34
  75. package/dist/system/fs/FolderCache.js +7 -38
  76. package/dist/system/proc.d.ts +51 -0
  77. package/dist/system/proc.js +148 -0
  78. package/dist/system/procRegistry.d.ts +52 -0
  79. package/dist/system/procRegistry.js +117 -0
  80. package/package.json +3 -1
  81. package/dist/orchestration/action/action.d.ts +0 -23
  82. package/dist/orchestration/action/action.js +0 -256
  83. package/dist/orchestration/action/types.d.ts +0 -182
  84. package/dist/orchestration/assert.d.ts +0 -22
  85. package/dist/orchestration/assert.js +0 -26
  86. package/dist/orchestration/spin/SpinHost.d.ts +0 -109
  87. package/dist/orchestration/spin/SpinHost.js +0 -337
  88. package/dist/orchestration/spin/spin.d.ts +0 -24
  89. package/dist/orchestration/spin/spin.js +0 -57
  90. package/dist/orchestration/spin/types.d.ts +0 -63
  91. package/dist/output/Output.d.ts +0 -24
  92. package/dist/output/Output.js +0 -27
  93. package/dist/output/index.d.ts +0 -11
  94. package/dist/output/index.js +0 -11
  95. package/dist/output/log/LogRenderer.d.ts +0 -73
  96. package/dist/output/log/LogRenderer.js +0 -133
  97. package/dist/output/log/LogsCollector.d.ts +0 -79
  98. package/dist/output/log/LogsCollector.js +0 -141
  99. package/dist/output/log/RunLog.d.ts +0 -47
  100. package/dist/output/log/RunLog.js +0 -142
  101. package/dist/output/log/console-capture.d.ts +0 -24
  102. package/dist/output/log/console-capture.js +0 -53
  103. package/dist/system/fs/streams.d.ts +0 -11
  104. package/dist/system/fs/streams.js +0 -14
  105. package/dist/system/process/process.d.ts +0 -35
  106. package/dist/system/process/process.js +0 -104
  107. package/dist/utils/serialize.d.ts +0 -2
  108. package/dist/utils/serialize.js +0 -12
  109. /package/dist/{orchestration/action → core}/types.js +0 -0
  110. /package/dist/{orchestration/spin/types.js → foundation/LogEntry.js} +0 -0
@@ -1,182 +0,0 @@
1
- import type { Logger } from "../../output/log/LogsCollector.ts";
2
- import type { Output } from "../../output/Output.ts";
3
- import type { Emitter, EventHandler } from "../events/events.ts";
4
- import type { Proc } from "../../system/process/process.ts";
5
- import type { Assert } from "../assert.ts";
6
- /** An action that declares no events. */
7
- export type NoEvents = Record<never, never>;
8
- /** A value an action's `exec` may return: a result, or a promise of one. */
9
- export type Awaitable<T> = T | Promise<T>;
10
- /**
11
- * How an action settles. An action **never throws into the spin body** — its
12
- * failure is data, not control flow: it completes with `{ ok: false, error }`
13
- * instead of rejecting. On success it carries the exec's `value`. `exitCode` is
14
- * present when the failure came from a process (see {@link import("../../../actions/command.ts").command}).
15
- * Await it via `instance.done` or `once("done")`; both resolve, never reject.
16
- */
17
- export type Outcome<T> = {
18
- readonly ok: true;
19
- readonly value: T;
20
- } | {
21
- readonly ok: false;
22
- readonly error: unknown;
23
- readonly exitCode?: number;
24
- };
25
- /**
26
- * Lifecycle events the framework emits for every action — on top of whatever it
27
- * declares via `.emits<…>()`. They make key moments first-class signals (no
28
- * log-scraping) and give a uniform sequencing hook.
29
- */
30
- export interface SystemEvents<Result> {
31
- /** The action published its imperative handle via `ctx.attach` (fires once). */
32
- attached: void;
33
- /** The action settled; payload is its {@link Outcome} (success or failure). Fires once, always. */
34
- done: Outcome<Result>;
35
- /** The action failed; payload is the thrown error. Fires only on failure (alongside `done`). */
36
- error: unknown;
37
- }
38
- /** The full event map a constructed instance exposes: the declared events plus the system ones. */
39
- export type InstanceEvents<Events extends object, Result> = Events & SystemEvents<Result>;
40
- /**
41
- * Launch an action into the spin and get the same instance back, so you can
42
- * `await` its `.done` (its {@link Outcome} — resolves with `{ ok, value } |
43
- * { ok, error }`, **never throws**), its `.ref` (the attached handle; rejects on
44
- * failure, so it's for data deps consumed inside other actions), or subscribe with
45
- * `.on` / `.once`. The spin body waves an action in with a nod; an action nods
46
- * **child** actions the same way, and their log output is prefixed with the
47
- * parent's path (see `SpinHost`).
48
- */
49
- export type Nod = <I extends AnyActionInstance>(instance: I) => I;
50
- /** System services the pipeline injects into every action when it runs. */
51
- export interface SystemGlobal {
52
- /** A logger bound to this action — feeds it directly or via `proc`. */
53
- readonly logs: Logger;
54
- /** The run's abort signal — aborted on teardown/cancel; honor it to stop early. */
55
- readonly signal: AbortSignal;
56
- /**
57
- * Launch a **child** action into the same spin (returns it for awaiting/chaining).
58
- * The child's log output is prefixed with this action's path (`parent › child`),
59
- * and the child lives in the spin — it keeps the run alive until it settles,
60
- * independent of when this action finishes. `await ctx.nod(x).done` to wait on it.
61
- */
62
- readonly nod: Nod;
63
- /**
64
- * This run's output subsystem — the folder, snapshot store, and run-log writer,
65
- * owned by the {@link SpinHost} and threaded to every action (rather than reached
66
- * as a module singleton). The framework uses it to place `ctx.artifactsFolder`
67
- * and to snapshot event/proc payloads; actions can reach `output.snapshots` to
68
- * capture their own artifacts.
69
- */
70
- readonly output: Output;
71
- /**
72
- * Assert a boolean invariant for this action. Logs a `⊨` line every call; a
73
- * mismatch is recorded into the run's verdict (never throws, never stops the
74
- * run). Bound to this action's path for attribution.
75
- */
76
- readonly assert: Assert;
77
- }
78
- /** What an action's implementation receives: the system bag, plus `emit`, `attach`, and `proc`. */
79
- export interface ActionContext<Events extends object, Handle = void> extends SystemGlobal {
80
- /** Emit one of this action's declared events (also logged onto the timeline under this action's path). */
81
- readonly emit: Emitter<Events>["emit"];
82
- /** Publish this action's imperative handle, resolving its instance's `.ref`. */
83
- readonly attach: (handle: Handle) => void;
84
- /**
85
- * Spawn child processes bound to this action: their output streams into this
86
- * action's log and they're killed on teardown. The built-in source; a custom
87
- * one (e.g. CDP) follows the same pattern over `logs`/`signal`.
88
- */
89
- readonly proc: Proc;
90
- /**
91
- * Absolute path to this run's output folder (`logs/<name>/<date>/<time>/`) —
92
- * created and shared across the run. Write action artifacts here so they land
93
- * beside `run.log`; the same folder is on the `spin` context.
94
- */
95
- readonly artifactsFolder: string;
96
- /**
97
- * Capture a JSON snapshot into this run's output folder and drop a `[snapshot]`
98
- * line on the timeline (under this action's path). The ergonomic shortcut for
99
- * `ctx.output.snapshots.snapshot(…)`; the same convenience is on the `spin`
100
- * context. Resolves with the snapshot's relative path.
101
- */
102
- readonly snapshot: (name: string, value: unknown) => Promise<string>;
103
- }
104
- /** A constructed-but-not-yet-run action: what calling an `Action` produces. */
105
- export interface ActionInstance<Result = unknown, Events extends object = NoEvents, Handle = void> {
106
- readonly name: string;
107
- readonly args: readonly unknown[];
108
- /**
109
- * Runs the action with the captured args and the injected system services.
110
- * **Resolves with the {@link Outcome}, never rejects** — a failing action is
111
- * reported as `{ ok: false, error }`, not a thrown promise, so it can't tear a
112
- * caller down. The run reads this to build its verdict.
113
- */
114
- readonly start: (system: SystemGlobal) => Promise<Outcome<Result>>;
115
- /**
116
- * The action's {@link Outcome}, as a memoized promise that **never rejects** —
117
- * the ergonomic way to await completion in a spin body:
118
- * `const r = await nod(x).done; if (!r.ok) …`. Same value as `once("done")`.
119
- */
120
- readonly done: Promise<Outcome<Result>>;
121
- /**
122
- * Resolves with the handle the action attached via `ctx.attach` (set once).
123
- * Used to thread a handle into another action (`chromePage("Explorer", driver.ref)`). If
124
- * the action settles *without* attaching, this resolves (`undefined` for the
125
- * default `void` handle) on success and **rejects on failure** — so, unlike
126
- * `done`, awaiting `ref` in a body can throw. It's meant to be consumed as a
127
- * constructor arg *inside* another action, where that rejection becomes that
128
- * action's failure Outcome. For control flow, await `done`.
129
- */
130
- readonly ref: Promise<Handle>;
131
- /** Subscribe to a declared or system event (`done`/`error`) with a handler. */
132
- on<K extends keyof InstanceEvents<Events, Result>>(key: K, handler: EventHandler<InstanceEvents<Events, Result>[K]>): void;
133
- /**
134
- * A promise for the next occurrence of an event. It **never rejects**: it
135
- * resolves with the event's payload, or with `undefined` if the action settles
136
- * (`done`) before the event ever fires — so awaiting it in a spin body can't
137
- * throw and can't hang. `once("done")` yields the {@link Outcome}. Use {@link on}
138
- * for ongoing/handler subscriptions; `once` is the awaitable, one-value form.
139
- */
140
- once<K extends keyof InstanceEvents<Events, Result>>(key: K): Promise<InstanceEvents<Events, Result>[K] | undefined>;
141
- /**
142
- * Gate this action on its **inputs** — the files/folders it *reads* (its
143
- * sources), **not** what it produces. `paths` (each a file or a folder) are
144
- * fingerprinted by mtime+size; the returned instance skips running when those
145
- * inputs are unchanged since the last successful run, and otherwise runs this
146
- * action and records the fingerprint. Pass inputs, not build outputs: caching
147
- * on an output would skip the very run that produces it.
148
- *
149
- * Every path must be **absolute** (throws otherwise). A cache hit resolves
150
- * immediately as success (result `undefined`) and logs a `cached, skipping`
151
- * line; a failure of the inner action records nothing, so it re-runs next time.
152
- *
153
- * Because `nod`/`run` launch an instance the moment they receive it, chain
154
- * `.withCache` *before* handing the instance in — passing the source dir:
155
- * `nod(command("build", "npm run build").withCache(srcAbs))`.
156
- */
157
- withCache(...paths: string[]): AnyActionInstance;
158
- }
159
- /**
160
- * Any constructed instance, whatever its result, events, or handle. The launch
161
- * surface ({@link ActionInstance}-takers like `run` and `Spin.run`) accepts this
162
- * so handle-bearing actions — those declaring `.ref<H>()` — can be launched: the
163
- * bare `ActionInstance` pins the handle to `void`, so an instance whose `ref` is
164
- * a `Promise<Page>` (not `Promise<void>`) would otherwise be rejected. `unknown`
165
- * for result/handle and `object` for events admit every concrete instance while
166
- * still excluding non-instances.
167
- */
168
- export type AnyActionInstance = ActionInstance<unknown, object, unknown>;
169
- /** A callable produced by `action`: invoking it builds an {@link ActionInstance}. */
170
- export interface Action<Args extends unknown[], Result, Events extends object = NoEvents, Handle = void> {
171
- (...args: Args): ActionInstance<Result, Events, Handle>;
172
- readonly actionName: string;
173
- }
174
- /** Intermediate step from `action(name)`: declare events/handle, then provide the impl. */
175
- export interface ActionBuilder<Events extends object, Handle = void> {
176
- /** Declare the typed event map this action emits. */
177
- emits<E extends object>(): ActionBuilder<E, Handle>;
178
- /** Declare the imperative handle this action exposes (resolved via `instance.ref`). */
179
- ref<H>(): ActionBuilder<Events, H>;
180
- /** Provide the implementation. `ctx` carries `logs`, `signal`, `emit`, and `attach`. */
181
- run<Args extends unknown[], Result>(exec: (ctx: ActionContext<Events, Handle>, ...args: Args) => Awaitable<Result>): Action<Args, Result, Events, Handle>;
182
- }
@@ -1,22 +0,0 @@
1
- import type { Logger } from "../output/log/LogsCollector.ts";
2
- /**
3
- * An inline check injected into every action's `ctx` and the `spin` body. Passes
4
- * when `value` is `true`. Every call is logged; a failure is recorded into the
5
- * run's verdict — as data, not a throw — so the run keeps going and later
6
- * assertions still fire. Returns the pass boolean so a body can branch on it.
7
- *
8
- * assert(page.isVisible(), "page loaded")
9
- */
10
- export type Assert = (value: boolean, what: string) => boolean;
11
- /** Marks a failed assertion in the spin's verdict, distinct from an action's own error. */
12
- export declare class AssertionError extends Error {
13
- constructor(message: string);
14
- }
15
- /**
16
- * Build an {@link Assert} bound to one action (or the spin body): `name` is its
17
- * launch path (used for attribution in the log line and the error), `logs` is the
18
- * logger to append to, and `onFail` records a genuine failure into the verdict. The
19
- * factory is file-agnostic — it only appends `level: "assert"` entries; the
20
- * dedicated `assertions.log` is a downstream projection of that stream.
21
- */
22
- export declare function createAssert(name: string, logs: Logger, onFail: (error: AssertionError) => void): Assert;
@@ -1,26 +0,0 @@
1
- /** Marks a failed assertion in the spin's verdict, distinct from an action's own error. */
2
- export class AssertionError extends Error {
3
- constructor(message) {
4
- super(message);
5
- this.name = "AssertionError";
6
- }
7
- }
8
- /**
9
- * Build an {@link Assert} bound to one action (or the spin body): `name` is its
10
- * launch path (used for attribution in the log line and the error), `logs` is the
11
- * logger to append to, and `onFail` records a genuine failure into the verdict. The
12
- * factory is file-agnostic — it only appends `level: "assert"` entries; the
13
- * dedicated `assertions.log` is a downstream projection of that stream.
14
- */
15
- export function createAssert(name, logs, onFail) {
16
- return (value, what) => {
17
- const pass = value;
18
- const glyph = pass ? "✓" : "✗";
19
- const message = `${name} · ${glyph} ${what}`;
20
- logs.append({ source: "assert", level: "assert", message });
21
- if (!pass) {
22
- onFail(new AssertionError(`${name}: ${what}`));
23
- }
24
- return pass;
25
- };
26
- }
@@ -1,109 +0,0 @@
1
- import type { AnyActionInstance } from "../action/types.ts";
2
- import { LogsCollector } from "../../output/log/LogsCollector.ts";
3
- import { Output } from "../../output/Output.ts";
4
- import type { SpinResult, SpinState } from "./types.ts";
5
- /**
6
- * A live spin over its own append-only log: constructing one launches the given
7
- * actions in parallel under a single abort controller and drives them to a terminal
8
- * {@link SpinResult}. Stop it with `.cancel()`, `await` its `.done` for the verdict.
9
- *
10
- * **A spin owns its world.** Each construction creates its *own* {@link LogsCollector}
11
- * and {@link Output} — the run's log, folder, and snapshots — rather than sharing a
12
- * process global, so runs don't bleed into each other and a fresh run can be created
13
- * in a living process (sequential re-runs, in-process tests). Attach further actions
14
- * to a running spin — including event-driven ones — via its `.run(…)` method.
15
- *
16
- * Each action runs against its own child logger (which headers and indents its
17
- * lines into this run's log) and the spin's shared abort signal — both injected
18
- * into its context, where the framework also binds a `proc` and the run's `output`
19
- * so child processes stream into the right log and die on teardown. A **failing
20
- * action never tears the spin down** — its `{ ok: false }` outcome is recorded in
21
- * the verdict (and sets exit code 1) and the spin keeps running. Teardown to
22
- * `closed` happens only on Ctrl-C, `.cancel()`, natural completion, or an uncaught
23
- * fatal. The process-level hooks (SIGINT/uncaught handlers, `exitCode`) are owned by
24
- * the run and cleaned at finalize, so they don't outlive it.
25
- */
26
- export declare class SpinHost {
27
- /** This run's append-only log — its own store, not a process global. */
28
- readonly logs: LogsCollector;
29
- /** This run's output subsystem (folder + snapshots + run-log writer), wired to {@link logs}. */
30
- readonly output: Output;
31
- /** The spin's abort signal — for consumers that want to observe teardown. */
32
- readonly signal: AbortSignal;
33
- /** Settles once the spin reaches `closed`, carrying the terminal verdict. Never rejects. */
34
- readonly done: Promise<SpinResult>;
35
- /** The original `console.log`, captured before patching — the run's terminal writer. */
36
- private readonly term;
37
- /** Reinstalls the pre-run console methods; called once at finalize. */
38
- private readonly restoreConsole;
39
- private readonly controller;
40
- private phase;
41
- /** The current lifecycle state — see {@link SpinState}. */
42
- get state(): SpinState;
43
- private readonly failures;
44
- private readonly inFlight;
45
- private readonly launched;
46
- private readonly streaming;
47
- private readonly onSigint;
48
- private readonly onUncaught;
49
- private readonly onUnhandled;
50
- constructor(...actions: AnyActionInstance[]);
51
- /**
52
- * Launch more actions into this same spin — same controller, same log. Chainable.
53
- *
54
- * Throws if the spin is no longer `open`.
55
- */
56
- run(...more: AnyActionInstance[]): this;
57
- /**
58
- * Launch `instance` into the spin under an optional `parentPath` (the nodding
59
- * action's path, or `undefined` for a top-level nod). The single guarded entry to
60
- * launching — `run` (top-level) and an action's own scoped `nod` both route here,
61
- * so the "spin is open" check lives in one place.
62
- */
63
- private nodInto;
64
- /** Begin teardown: abort the signal (killing procs, unblocking waits, stopping daemons). */
65
- cancel(): void;
66
- /**
67
- * Live-render the log to the console. Started automatically from the constructor
68
- * — every spin drains, with no flag to disable and nothing for the caller to
69
- * invoke (for now). A single {@link LogRenderer} applies grouping/indentation as
70
- * entries stream by; its state spans the replayed history and live entries as one
71
- * continuous stream. The subscription ends itself when the run tears down (the
72
- * collector closes it), so this loop simply runs to completion.
73
- */
74
- private drain;
75
- /** Narrate a run-lifecycle milestone into this run's log (renders as a `[run]` line). */
76
- private narrate;
77
- /**
78
- * Move `open → closing`: abort the signal (unwinding procs, waits, and
79
- * daemons), narrate the cause once, and stop accepting new actions. Idempotent
80
- * — only the first caller transitions; later triggers (a second failure, a
81
- * `cancel()` during teardown) are no-ops. This is the *only* path to abort, so
82
- * every teardown reason flows through one place.
83
- */
84
- private beginClosing;
85
- /**
86
- * Record a genuine fault in the verdict. It does **not** tear the spin down — a
87
- * failing action is data, and the spin keeps running (only Ctrl-C / cancel /
88
- * completion / an uncaught fatal close it).
89
- */
90
- private recordFailure;
91
- /**
92
- * A throw or rejection that escaped every action's own handling. Without a
93
- * handler Node prints it and exits *immediately* — before the run can flush its
94
- * log, which leaves a silent, empty log directory and no verdict. So catch it,
95
- * log it, fold it into the verdict, and drive teardown so the log still flushes
96
- * and the exit code reflects the fault — degrading a stray throw to a recorded
97
- * failure instead of a hard crash. Removed at finalize, so it only governs
98
- * while this run owns the process.
99
- */
100
- private onFatal;
101
- private launch;
102
- /**
103
- * Drives the run to `closed`. The loop re-checks `inFlight` because actions can
104
- * be added (or daemons can settle on abort) while we await. A synchronous
105
- * `.run(…)` from a settling action's lifecycle event lands before that action's
106
- * `finally` removes it, so `inFlight` never spuriously hits 0 mid-handoff.
107
- */
108
- private drive;
109
- }