opensip-cli 0.1.8 → 0.1.9

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 (138) hide show
  1. package/dist/bootstrap/admit-tool-package.d.ts +51 -11
  2. package/dist/bootstrap/admit-tool-package.d.ts.map +1 -1
  3. package/dist/bootstrap/admit-tool-package.js +46 -12
  4. package/dist/bootstrap/admit-tool-package.js.map +1 -1
  5. package/dist/bootstrap/baseline-seams.js +1 -1
  6. package/dist/bootstrap/baseline-seams.js.map +1 -1
  7. package/dist/bootstrap/bind-external-dispatch.d.ts +36 -0
  8. package/dist/bootstrap/bind-external-dispatch.d.ts.map +1 -0
  9. package/dist/bootstrap/bind-external-dispatch.js +81 -0
  10. package/dist/bootstrap/bind-external-dispatch.js.map +1 -0
  11. package/dist/bootstrap/build-command-registration-input.d.ts +13 -2
  12. package/dist/bootstrap/build-command-registration-input.d.ts.map +1 -1
  13. package/dist/bootstrap/build-command-registration-input.js +29 -2
  14. package/dist/bootstrap/build-command-registration-input.js.map +1 -1
  15. package/dist/bootstrap/build-per-run-scope.d.ts.map +1 -1
  16. package/dist/bootstrap/build-per-run-scope.js +19 -2
  17. package/dist/bootstrap/build-per-run-scope.js.map +1 -1
  18. package/dist/bootstrap/config-and-capabilities.d.ts +21 -6
  19. package/dist/bootstrap/config-and-capabilities.d.ts.map +1 -1
  20. package/dist/bootstrap/config-and-capabilities.js +79 -23
  21. package/dist/bootstrap/config-and-capabilities.js.map +1 -1
  22. package/dist/bootstrap/dispatch-external-tool-command.d.ts +67 -0
  23. package/dist/bootstrap/dispatch-external-tool-command.d.ts.map +1 -0
  24. package/dist/bootstrap/dispatch-external-tool-command.js +79 -0
  25. package/dist/bootstrap/dispatch-external-tool-command.js.map +1 -0
  26. package/dist/bootstrap/dispatch-external-tool-hook.d.ts +47 -0
  27. package/dist/bootstrap/dispatch-external-tool-hook.d.ts.map +1 -0
  28. package/dist/bootstrap/dispatch-external-tool-hook.js +49 -0
  29. package/dist/bootstrap/dispatch-external-tool-hook.js.map +1 -0
  30. package/dist/bootstrap/dispatch-fork-core.d.ts +48 -0
  31. package/dist/bootstrap/dispatch-fork-core.d.ts.map +1 -0
  32. package/dist/bootstrap/dispatch-fork-core.js +214 -0
  33. package/dist/bootstrap/dispatch-fork-core.js.map +1 -0
  34. package/dist/bootstrap/dispatch-host-rpc-handler.d.ts +27 -0
  35. package/dist/bootstrap/dispatch-host-rpc-handler.d.ts.map +1 -0
  36. package/dist/bootstrap/dispatch-host-rpc-handler.js +175 -0
  37. package/dist/bootstrap/dispatch-host-rpc-handler.js.map +1 -0
  38. package/dist/bootstrap/dispatch-replay-result.d.ts +51 -0
  39. package/dist/bootstrap/dispatch-replay-result.d.ts.map +1 -0
  40. package/dist/bootstrap/dispatch-replay-result.js +76 -0
  41. package/dist/bootstrap/dispatch-replay-result.js.map +1 -0
  42. package/dist/bootstrap/execute-post-bailout-bootstrap.d.ts.map +1 -1
  43. package/dist/bootstrap/execute-post-bailout-bootstrap.js +3 -1
  44. package/dist/bootstrap/execute-post-bailout-bootstrap.js.map +1 -1
  45. package/dist/bootstrap/owning-tool-init.d.ts +8 -2
  46. package/dist/bootstrap/owning-tool-init.d.ts.map +1 -1
  47. package/dist/bootstrap/owning-tool-init.js +11 -1
  48. package/dist/bootstrap/owning-tool-init.js.map +1 -1
  49. package/dist/bootstrap/register-authored-tools.d.ts +49 -0
  50. package/dist/bootstrap/register-authored-tools.d.ts.map +1 -0
  51. package/dist/bootstrap/register-authored-tools.js +132 -0
  52. package/dist/bootstrap/register-authored-tools.js.map +1 -0
  53. package/dist/bootstrap/register-tools-discovery.d.ts +0 -32
  54. package/dist/bootstrap/register-tools-discovery.d.ts.map +1 -1
  55. package/dist/bootstrap/register-tools-discovery.js +36 -100
  56. package/dist/bootstrap/register-tools-discovery.js.map +1 -1
  57. package/dist/bootstrap/register-tools-mount.d.ts.map +1 -1
  58. package/dist/bootstrap/register-tools-mount.js +20 -44
  59. package/dist/bootstrap/register-tools-mount.js.map +1 -1
  60. package/dist/bootstrap/register-tools.d.ts +2 -1
  61. package/dist/bootstrap/register-tools.d.ts.map +1 -1
  62. package/dist/bootstrap/register-tools.js +2 -1
  63. package/dist/bootstrap/register-tools.js.map +1 -1
  64. package/dist/bootstrap/run-plane.d.ts +11 -0
  65. package/dist/bootstrap/run-plane.d.ts.map +1 -1
  66. package/dist/bootstrap/run-plane.js.map +1 -1
  67. package/dist/bootstrap/synthesize-external-tool.d.ts +45 -0
  68. package/dist/bootstrap/synthesize-external-tool.d.ts.map +1 -0
  69. package/dist/bootstrap/synthesize-external-tool.js +112 -0
  70. package/dist/bootstrap/synthesize-external-tool.js.map +1 -0
  71. package/dist/bootstrap/tool-command-dispatch-types.d.ts +280 -0
  72. package/dist/bootstrap/tool-command-dispatch-types.d.ts.map +1 -0
  73. package/dist/bootstrap/tool-command-dispatch-types.js +34 -0
  74. package/dist/bootstrap/tool-command-dispatch-types.js.map +1 -0
  75. package/dist/bootstrap/tool-command-worker-config-pass.d.ts +24 -0
  76. package/dist/bootstrap/tool-command-worker-config-pass.d.ts.map +1 -0
  77. package/dist/bootstrap/tool-command-worker-config-pass.js +52 -0
  78. package/dist/bootstrap/tool-command-worker-config-pass.js.map +1 -0
  79. package/dist/bootstrap/tool-command-worker-context.d.ts +55 -0
  80. package/dist/bootstrap/tool-command-worker-context.d.ts.map +1 -0
  81. package/dist/bootstrap/tool-command-worker-context.js +163 -0
  82. package/dist/bootstrap/tool-command-worker-context.js.map +1 -0
  83. package/dist/bootstrap/tool-command-worker-entry.d.ts +66 -0
  84. package/dist/bootstrap/tool-command-worker-entry.d.ts.map +1 -0
  85. package/dist/bootstrap/tool-command-worker-entry.js +298 -0
  86. package/dist/bootstrap/tool-command-worker-entry.js.map +1 -0
  87. package/dist/bootstrap/tool-command-worker-rpc.d.ts +53 -0
  88. package/dist/bootstrap/tool-command-worker-rpc.d.ts.map +1 -0
  89. package/dist/bootstrap/tool-command-worker-rpc.js +78 -0
  90. package/dist/bootstrap/tool-command-worker-rpc.js.map +1 -0
  91. package/dist/bootstrap/tool-provenance.d.ts +85 -0
  92. package/dist/bootstrap/tool-provenance.d.ts.map +1 -0
  93. package/dist/bootstrap/tool-provenance.js +101 -0
  94. package/dist/bootstrap/tool-provenance.js.map +1 -0
  95. package/dist/cli-context.d.ts +17 -0
  96. package/dist/cli-context.d.ts.map +1 -1
  97. package/dist/cli-context.js +62 -1
  98. package/dist/cli-context.js.map +1 -1
  99. package/dist/commands/completion.d.ts.map +1 -1
  100. package/dist/commands/completion.js +3 -0
  101. package/dist/commands/completion.js.map +1 -1
  102. package/dist/commands/host-command-specs.d.ts +13 -15
  103. package/dist/commands/host-command-specs.d.ts.map +1 -1
  104. package/dist/commands/host-command-specs.js +27 -27
  105. package/dist/commands/host-command-specs.js.map +1 -1
  106. package/dist/commands/host-subcommand-groups.d.ts.map +1 -1
  107. package/dist/commands/host-subcommand-groups.js +63 -5
  108. package/dist/commands/host-subcommand-groups.js.map +1 -1
  109. package/dist/commands/internal-command-visibility.d.ts +13 -4
  110. package/dist/commands/internal-command-visibility.d.ts.map +1 -1
  111. package/dist/commands/internal-command-visibility.js +14 -5
  112. package/dist/commands/internal-command-visibility.js.map +1 -1
  113. package/dist/commands/mount-command-spec.d.ts.map +1 -1
  114. package/dist/commands/mount-command-spec.js +31 -0
  115. package/dist/commands/mount-command-spec.js.map +1 -1
  116. package/dist/commands/session-show.d.ts.map +1 -1
  117. package/dist/commands/session-show.js +4 -1
  118. package/dist/commands/session-show.js.map +1 -1
  119. package/dist/commands/tools/data-purge.js +2 -2
  120. package/dist/commands/tools/data-purge.js.map +1 -1
  121. package/dist/commands/tools/validate.js +1 -1
  122. package/dist/env/host-env-specs.d.ts.map +1 -1
  123. package/dist/env/host-env-specs.js +6 -0
  124. package/dist/env/host-env-specs.js.map +1 -1
  125. package/dist/index.js +6 -1
  126. package/dist/index.js.map +1 -1
  127. package/dist/report-compose.d.ts.map +1 -1
  128. package/dist/report-compose.js +85 -19
  129. package/dist/report-compose.js.map +1 -1
  130. package/dist/session-replay-registry.d.ts +33 -6
  131. package/dist/session-replay-registry.d.ts.map +1 -1
  132. package/dist/session-replay-registry.js +43 -6
  133. package/dist/session-replay-registry.js.map +1 -1
  134. package/dist/telemetry/profiling.d.ts +30 -0
  135. package/dist/telemetry/profiling.d.ts.map +1 -1
  136. package/dist/telemetry/profiling.js +16 -1
  137. package/dist/telemetry/profiling.js.map +1 -1
  138. package/package.json +32 -32
@@ -0,0 +1,298 @@
1
+ /**
2
+ * tool-command-worker-entry — the WORKER side of the out-of-process external
3
+ * tool command dispatch plane (ADR-0054, increments M4-C / M4-D / M4-E).
4
+ *
5
+ * This is a HOST internal `CommandSpec` (`__tool-command-worker`), forked by the
6
+ * supervisor as `node <cliScript> __tool-command-worker <specPath> --cwd <cwd>`.
7
+ * Forking the CLI binary as a subcommand (the SAME pattern graph's
8
+ * `graph-run-worker` uses) means the FULL CLI bootstrap runs first: the preAction
9
+ * hook discovers + imports the external tool runtime IN THE WORKER, registers it,
10
+ * runs its `contributeScope`, composes + validates config, and builds the full
11
+ * per-run scope — so by the time this handler runs, `currentScope()` carries the
12
+ * tool's subscope (`scope.fitness`/…), the check/recipe registries, project
13
+ * context, and `toolConfig` exactly as an in-process run (ADR-0054 M4-C `scope`
14
+ * mapping: "the worker re-bootstraps its OWN scope … exactly like graph's
15
+ * worker"). This is the isolation move — the untrusted runtime loads HERE, in the
16
+ * worker, never in the host.
17
+ *
18
+ * The handler then resolves the dispatched tool from the re-bootstrapped registry
19
+ * and runs ITS command handler against the WORKER-side `ToolCliContext` shim
20
+ * (`tool-command-worker-context.ts`): FRR seams (render/json/envelope/raw/error/
21
+ * exit) record the value and return it once in the {@link ToolCommandResult}; the
22
+ * host-RPC seams (datastore / egress / SARIF / baselines / toolState / hostPlanes
23
+ * / report-open / exit-code re-affirm) UPCALL the host over the rpc-reply channel
24
+ * (the host performs the privileged effect — datastore/network/FS/exit stay
25
+ * host-owned). Only the live-view seams fail loud (`unsupported-seam`).
26
+ *
27
+ * A handler that calls `process.exit`, throws, crashes the native layer, or spins
28
+ * the event loop is contained: the supervisor turns a premature child exit /
29
+ * timeout / `error` message into a structured parent-side failure, and the host
30
+ * process survives.
31
+ */
32
+ import { readFileSync } from 'node:fs';
33
+ import { createRunTimer, currentScope, defineCommand, resolveToolHooks, } from '@opensip-cli/core';
34
+ import { runDeepConfigPass } from './tool-command-worker-config-pass.js';
35
+ import { buildWorkerContext, UnsupportedSeamError, } from './tool-command-worker-context.js';
36
+ import { createWorkerRpcClient } from './tool-command-worker-rpc.js';
37
+ /** Post one IPC message to the parent (no-op when not forked — e.g. a unit call). */
38
+ function send(msg) {
39
+ process.send?.(msg);
40
+ }
41
+ /**
42
+ * Resolve the dispatched tool from the re-bootstrapped registry. The bootstrap
43
+ * already imported + registered it (the isolation import happened in this worker
44
+ * during preAction). Match by the registry's human key first, then by stable id /
45
+ * human name — symmetric to the host provenance/dispatch matchers.
46
+ *
47
+ * @throws {Error & {failureClass}} `runtime-load-failed` when the tool is not in
48
+ * the worker's registry (the bootstrap did not admit it — e.g. a trust-policy
49
+ * or discovery miss). Surfaces as a structured IPC error; the host survives.
50
+ */
51
+ function resolveTool(spec) {
52
+ const tools = currentScope()?.tools;
53
+ const tool = tools?.get(spec.toolId) ??
54
+ tools?.list().find((t) => t.metadata.id === spec.toolId || t.metadata.name === spec.toolId);
55
+ if (tool === undefined) {
56
+ const err = new Error(`tool command worker: tool '${spec.toolId}' is not registered in the worker scope ` +
57
+ '(the bootstrap did not discover/admit it — check provenance/trust policy)');
58
+ err.failureClass = 'runtime-load-failed';
59
+ throw err;
60
+ }
61
+ return tool;
62
+ }
63
+ /** Resolve the command spec the worker should run, or throw `command-not-found`. */
64
+ function findCommandSpec(tool, commandName) {
65
+ const spec = tool.commandSpecs?.find((s) => s.name === commandName);
66
+ if (spec === undefined) {
67
+ const err = new Error(`tool command worker: tool '${tool.metadata.id}' has no command '${commandName}'`);
68
+ err.failureClass = 'command-not-found';
69
+ throw err;
70
+ }
71
+ return spec;
72
+ }
73
+ /**
74
+ * ADR-0054 M4-F: run the dispatched tool's `initialize()` once, worker-side,
75
+ * before its handler. The host no longer runs an EXTERNAL owning tool's
76
+ * `initialize` (that would execute untrusted runtime in the kernel) — and the
77
+ * worker bootstraps the host `__tool-command-worker` subcommand (owned by NO
78
+ * tool), so the worker's own preflight never resolves the dispatched tool as the
79
+ * "owning tool". Running it here is the only place an external tool's
80
+ * `initialize` runs under worker dispatch. A throw propagates to
81
+ * {@link runToolCommandWorker}'s catch → structured `tool-handler-throw`; the
82
+ * host survives (fail loud, never a half-initialised silent run).
83
+ */
84
+ async function runWorkerInitialize(tool) {
85
+ const initialize = resolveToolHooks(tool).initialize;
86
+ if (initialize === undefined)
87
+ return;
88
+ await initialize();
89
+ }
90
+ /** Build a structured `error` IPC message with a failure class (+ stack when present). */
91
+ function errorMessage(message, failureClass, stack) {
92
+ return {
93
+ kind: 'error',
94
+ message,
95
+ failureClass,
96
+ ...(stack === undefined ? {} : { stack }),
97
+ };
98
+ }
99
+ /** Read + parse the worker spec file, or return a `bad-spec` error message. */
100
+ function readSpec(specPath) {
101
+ try {
102
+ return JSON.parse(readFileSync(specPath, 'utf8'));
103
+ }
104
+ catch (error) {
105
+ return errorMessage(`tool command worker: unreadable spec at '${specPath}': ${error instanceof Error ? error.message : String(error)}`, 'bad-spec');
106
+ }
107
+ }
108
+ /**
109
+ * The output modes whose PAYLOAD is the handler's RETURN value (routed by the
110
+ * in-process `dispatchOutput`): `command-result` (a `CommandResult`) and
111
+ * `signal-envelope` (a `SignalEnvelope`). For these, the worker must carry the
112
+ * return back UNROUTED in `returned` so the supervisor replays it through the SAME
113
+ * `dispatchOutput`. `raw-stream` / `live-view` produce no routable return payload.
114
+ */
115
+ function isReturnValuedOutput(output) {
116
+ return output === 'command-result' || output === 'signal-envelope';
117
+ }
118
+ /** Drain the accumulator + the handler's return into a serializable result. */
119
+ function toResult(output, acc, session, returned) {
120
+ return {
121
+ output,
122
+ ...(acc.render === undefined ? {} : { render: acc.render }),
123
+ ...(acc.envelope === undefined ? {} : { envelope: acc.envelope }),
124
+ ...(acc.json === undefined ? {} : { json: acc.json }),
125
+ ...(acc.raw === undefined ? {} : { raw: acc.raw }),
126
+ ...(acc.error === undefined ? {} : { error: acc.error }),
127
+ ...(acc.exitCode === undefined ? {} : { exitCode: acc.exitCode }),
128
+ ...(session === undefined ? {} : { session }),
129
+ // Carry the handler's return for the return-valued modes so the supervisor
130
+ // routes it via the same `dispatchOutput` the in-process path uses (parity).
131
+ ...(returned === undefined || !isReturnValuedOutput(output) ? {} : { returned }),
132
+ };
133
+ }
134
+ /** Map a thrown error to its structured failure class for the IPC `error` message. */
135
+ function classifyThrow(error) {
136
+ if (error instanceof UnsupportedSeamError)
137
+ return error.failureClass;
138
+ return error.failureClass ?? 'tool-handler-throw';
139
+ }
140
+ /**
141
+ * Resolve the dispatched tool from the re-bootstrapped scope, run the deep config
142
+ * pass, then run its command handler against the worker-side context shim, and
143
+ * build the result. Throws on a handler error (caught by
144
+ * {@link runToolCommandWorker}); returns an `error` message for the structured
145
+ * pre-handler failures (config-invalid; tool / command-not-found are thrown with
146
+ * a failureClass tag).
147
+ *
148
+ * `currentScope()` here is the FULL per-run scope the CLI bootstrap built for the
149
+ * `__tool-command-worker` subcommand (project/config/registries/contributeScope),
150
+ * so the handler reads `cli.scope.toolConfig`/`cli.scope.<subscope>`/checks
151
+ * worker-LOCAL while datastore/egress cross to the host via the RPC shim.
152
+ */
153
+ async function runLoadedCommand(spec) {
154
+ const tool = resolveTool(spec);
155
+ // ADR-0054 M4-F hook mode: when the spec names a lifecycle HOOK (not a command),
156
+ // run that hook worker-side and return its plain-data result. This is how the
157
+ // host gathers an external tool's `collectReportData` / `sessionReplay` without
158
+ // executing the untrusted runtime in the kernel process.
159
+ if (spec.hook !== undefined) {
160
+ return await runLoadedHook(tool, spec);
161
+ }
162
+ if (spec.commandName === undefined) {
163
+ return errorMessage(`tool command worker: spec for tool '${spec.toolId}' names neither a command nor a hook`, 'bad-spec');
164
+ }
165
+ const commandSpec = findCommandSpec(tool, spec.commandName);
166
+ // ADR-0054 M4-E DEEP config pass: run the tool's REAL Zod against its config
167
+ // namespace IN THE WORKER (the host validated only the coarse manifest shape
168
+ // pre-fork). A failure crosses IPC as `config-invalid` — never a host crash —
169
+ // and the supervisor maps it to the SAME typed config error the host coarse
170
+ // pass uses. Runs BEFORE building the context: a config failure must
171
+ // short-circuit before any handler effect.
172
+ const configFailure = runDeepConfigPass(tool, spec.config);
173
+ if (configFailure !== undefined)
174
+ return errorMessage(configFailure, 'config-invalid');
175
+ // ADR-0054 M4-F: run the dispatched tool's `initialize()` worker-side before the
176
+ // handler (the host no longer runs an external owning tool's initialize). A
177
+ // throw becomes a structured `tool-handler-throw` via the outer catch.
178
+ await runWorkerInitialize(tool);
179
+ // The host-RPC upcall client over the live IPC channel (M4-C). `process` is the
180
+ // duplex: requests post via `process.send`; replies arrive on
181
+ // `process.on('message')`. Disposed in the finally so the listener is removed.
182
+ const rpcClient = createWorkerRpcClient(process);
183
+ // The handler runs against the bootstrapped scope (worker-local reads) but with
184
+ // the WORKER context shim (FRR records + RPC upcalls for privileged effects).
185
+ const scope = currentScope();
186
+ if (scope === undefined) {
187
+ return errorMessage('tool command worker: no scope is entered (bootstrap did not run before the worker handler)', 'runtime-load-failed');
188
+ }
189
+ try {
190
+ const acc = {};
191
+ const ctx = buildWorkerContext(scope, createRunTimer(), acc, rpcClient);
192
+ // Run the handler. A `process.exit` / crash / hang here is contained by the
193
+ // supervisor (premature-exit / timeout → structured parent failure); a throw
194
+ // propagates to runToolCommandWorker's catch and becomes a structured error.
195
+ // The handler's RETURN serves two roles: (1) for `command-result` /
196
+ // `signal-envelope` it IS the output payload (routed by `dispatchOutput`
197
+ // host-side); (2) it may carry a `session` leg (ToolRunCompletion) the host
198
+ // persists after the worker resolves (host-owned-run-timing). Capture it once.
199
+ const returned = (await commandSpec.handler({ ...spec.opts, _args: spec.positionals }, ctx));
200
+ return {
201
+ kind: 'result',
202
+ value: toResult(commandSpec.output, acc, returned?.session, returned),
203
+ };
204
+ }
205
+ finally {
206
+ // @fitness-ignore-next-line detached-promises -- WorkerRpcClient.dispose() returns void (removes the reply listener + clears the pending map, synchronous); the name-based heuristic misfires inside this async fn.
207
+ rpcClient.dispose();
208
+ }
209
+ }
210
+ /**
211
+ * ADR-0054 M4-F: run a dispatched tool's LIFECYCLE HOOK worker-side and return its
212
+ * plain-data result in `hookResult`. This is how the host gathers an EXTERNAL
213
+ * tool's `collectReportData` / `sessionReplay` data WITHOUT executing the
214
+ * untrusted runtime in the kernel process. The hook runs against the worker's own
215
+ * re-bootstrapped scope (`currentScope()`), exactly the contract the in-host path
216
+ * gives a bundled tool — just inside the isolation boundary. The host owns the
217
+ * merge/render/replay-emit (privileged effect). A throw propagates to
218
+ * {@link runToolCommandWorker}'s catch → structured failure; the host survives.
219
+ *
220
+ * The hook's runtime is resolved from the re-bootstrapped registry (the worker
221
+ * `initialize()` does NOT run for a hook-mode dispatch — hooks read data; an
222
+ * external tool that needs `initialize` for its report/replay should run it in
223
+ * the hook body, which is OUT of M4-F's first-party scope).
224
+ */
225
+ async function runLoadedHook(tool, spec) {
226
+ const scope = currentScope();
227
+ if (scope === undefined) {
228
+ return errorMessage('tool command worker: no scope is entered for the hook run (bootstrap did not run)', 'runtime-load-failed');
229
+ }
230
+ const hooks = resolveToolHooks(tool);
231
+ // `collectReportData(scope)` takes the tool-facing ToolScope view; the worker
232
+ // RunScope IS a ToolScope (it extends it), so pass it directly — it returns a
233
+ // plain-data Record<string, unknown>. `sessionReplay` rebuilds the
234
+ // ToolSessionReplay from the stored row (`hookArg` is the serialized
235
+ // ToolSessionRecord the host read from the datastore).
236
+ const hookResult = spec.hook === 'collectReportData'
237
+ ? await hooks.collectReportData?.(scope)
238
+ : hooks.sessionReplay?.replaySession(spec.hookArg);
239
+ return {
240
+ kind: 'result',
241
+ // `output` is required on the result; the host never replays output for a
242
+ // hook-mode dispatch (it reads `hookResult`), so a benign default suffices.
243
+ value: { output: 'command-result', ...(hookResult === undefined ? {} : { hookResult }) },
244
+ };
245
+ }
246
+ /**
247
+ * The testable core: produce the {@link DispatchWorkerMessage} the worker would
248
+ * post, without touching `process.send`. Never throws — every failure becomes a
249
+ * structured `error` message (the supervisor rejects on it). Must run inside an
250
+ * entered scope (the bootstrap enters it for the real subcommand; unit tests wrap
251
+ * it in `runWithScope`).
252
+ */
253
+ export async function runToolCommandWorker(specPath) {
254
+ const spec = readSpec(specPath);
255
+ if ('kind' in spec)
256
+ return spec; // bad-spec error message
257
+ try {
258
+ return await runLoadedCommand(spec);
259
+ }
260
+ catch (error) {
261
+ return errorMessage(error instanceof Error ? error.message : String(error), classifyThrow(error), error instanceof Error ? error.stack : undefined);
262
+ }
263
+ }
264
+ /**
265
+ * Run one external tool command headless in this worker and post the slim
266
+ * {@link ToolCommandResult} (or a structured `error`) over IPC. Never throws to
267
+ * the caller — every failure becomes an `error` IPC message so the supervisor
268
+ * rejects cleanly. This is the host CommandSpec handler's body.
269
+ */
270
+ export async function executeToolCommandWorker(specPath) {
271
+ // @fitness-ignore-next-line detached-promises -- the promise IS awaited; `send(...)` is a synchronous void IPC post of the already-resolved value. The name-based heuristic misfires on `send(await ...)`.
272
+ send(await runToolCommandWorker(specPath));
273
+ }
274
+ /**
275
+ * `__tool-command-worker <specPath>` — the [internal] host subcommand the
276
+ * dispatch supervisor forks. Mirrors `graphRunWorkerCommandSpec`: `raw-stream`
277
+ * (it owns its own IPC output surface), `scope: 'project'` (the full bootstrap
278
+ * runs first), `visibility: 'internal'`. The supervisor passes `--cwd` so the
279
+ * bootstrap targets the right project. The handler ignores the host `ctx` it is
280
+ * given (the worker builds its OWN context shim over the bootstrapped scope) and
281
+ * posts the result over the IPC channel.
282
+ */
283
+ export const toolCommandWorkerCommandSpec = defineCommand({
284
+ name: '__tool-command-worker',
285
+ visibility: 'internal',
286
+ description: '[internal] Run one external tool command headless in a forked worker and stream the result over IPC (forked by the ADR-0054 dispatch supervisor)',
287
+ // The supervisor passes `--cwd`; bootstrap uses it to resolve the project.
288
+ commonFlags: ['cwd'],
289
+ args: [{ name: 'specPath', description: 'Path to the JSON tool-command worker spec file' }],
290
+ scope: 'project',
291
+ output: 'raw-stream',
292
+ rawStreamReason: 'worker-ipc',
293
+ handler: async (rawOpts) => {
294
+ const specPath = rawOpts._args?.[0] ?? '';
295
+ await executeToolCommandWorker(specPath);
296
+ },
297
+ });
298
+ //# sourceMappingURL=tool-command-worker-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-command-worker-entry.js","sourceRoot":"","sources":["../../src/bootstrap/tool-command-worker-entry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * tool-command-worker-rpc — the WORKER side of the ADR-0054 host-RPC upcall
3
+ * channel (increment M4-C).
4
+ *
5
+ * A host-RPC seam in the worker shim (`tool-command-worker-context.ts`) calls
6
+ * `hostRpc(request)`: it stamps a monotonic `rpcId`, posts the request to the
7
+ * parent over the transport's `progress` arm
8
+ * (`WorkerMessage<HostRpcRequest, ToolCommandResult>`), and BLOCKS on the
9
+ * matching {@link RpcReply} the parent posts back via `child.send`. The host
10
+ * performs the privileged effect through the real `ToolCliContext` and replies;
11
+ * a host-side fault crosses back as `{ ok: false, error }`, which this re-throws
12
+ * so the handler sees a normal thrown error (never a host crash, never a silent
13
+ * no-op).
14
+ *
15
+ * Replies are delivered on the worker's single `process.on('message')` listener,
16
+ * installed once and demultiplexed by `rpcId` into the pending-request map. The
17
+ * listener ignores anything that is not an `rpc-reply` (the channel is otherwise
18
+ * child → parent), so it never races the worker's own outbound posts.
19
+ */
20
+ import type { HostRpcCall, HostRpcRequest } from './tool-command-dispatch-types.js';
21
+ import type { WorkerMessage } from '@opensip-cli/core';
22
+ /** The worker's outbound IPC type binding: requests stream on `progress`. */
23
+ type WorkerOutbound = WorkerMessage<HostRpcRequest, unknown>;
24
+ /**
25
+ * A worker RPC client: `call` issues one upcall and resolves with the host's
26
+ * reply value (or rejects with the host's structured error). `dispose` removes
27
+ * the reply listener (hygiene for the short-lived fork; the process exits on
28
+ * settle regardless).
29
+ */
30
+ export interface WorkerRpcClient {
31
+ /**
32
+ * Issue one host-RPC upcall (sans `rpcId` — assigned here) and await the
33
+ * host's reply.
34
+ *
35
+ * @throws {Error} when the host reply is `{ ok: false }` (the host-side fault
36
+ * re-thrown into the handler), or when the worker is not forked with an IPC
37
+ * channel (no `process.send`, an integration-only misuse).
38
+ */
39
+ readonly call: (request: HostRpcCall) => Promise<unknown>;
40
+ readonly dispose: () => void;
41
+ }
42
+ /**
43
+ * Create the worker's RPC client over the live IPC channel. Installs the single
44
+ * reply listener; `send` posts requests on the `progress` arm. The caller passes
45
+ * `process` so unit tests can supply a fake duplex.
46
+ */
47
+ export declare function createWorkerRpcClient(channel: {
48
+ send?: (msg: WorkerOutbound) => unknown;
49
+ on: (event: 'message', listener: (msg: unknown) => void) => unknown;
50
+ off?: (event: 'message', listener: (msg: unknown) => void) => unknown;
51
+ }): WorkerRpcClient;
52
+ export {};
53
+ //# sourceMappingURL=tool-command-worker-rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-command-worker-rpc.d.ts","sourceRoot":"","sources":["../../src/bootstrap/tool-command-worker-rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAY,MAAM,kCAAkC,CAAC;AAC9F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,6EAA6E;AAC7E,KAAK,cAAc,GAAG,aAAa,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAyBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC;IACxC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAAK,OAAO,CAAC;IACpE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAAK,OAAO,CAAC;CACvE,GAAG,eAAe,CAuClB"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * tool-command-worker-rpc — the WORKER side of the ADR-0054 host-RPC upcall
3
+ * channel (increment M4-C).
4
+ *
5
+ * A host-RPC seam in the worker shim (`tool-command-worker-context.ts`) calls
6
+ * `hostRpc(request)`: it stamps a monotonic `rpcId`, posts the request to the
7
+ * parent over the transport's `progress` arm
8
+ * (`WorkerMessage<HostRpcRequest, ToolCommandResult>`), and BLOCKS on the
9
+ * matching {@link RpcReply} the parent posts back via `child.send`. The host
10
+ * performs the privileged effect through the real `ToolCliContext` and replies;
11
+ * a host-side fault crosses back as `{ ok: false, error }`, which this re-throws
12
+ * so the handler sees a normal thrown error (never a host crash, never a silent
13
+ * no-op).
14
+ *
15
+ * Replies are delivered on the worker's single `process.on('message')` listener,
16
+ * installed once and demultiplexed by `rpcId` into the pending-request map. The
17
+ * listener ignores anything that is not an `rpc-reply` (the channel is otherwise
18
+ * child → parent), so it never races the worker's own outbound posts.
19
+ */
20
+ /** Reconstruct an Error from a host reply's structured error (preserving stack). */
21
+ function rpcError(detail) {
22
+ const err = new Error(detail.message);
23
+ if (detail.code !== undefined)
24
+ err.code = detail.code;
25
+ if (detail.stack !== undefined)
26
+ err.stack = detail.stack;
27
+ return err;
28
+ }
29
+ /** Narrow an arbitrary IPC message to a {@link RpcReply}. */
30
+ function isRpcReply(msg) {
31
+ return (typeof msg === 'object' &&
32
+ msg !== null &&
33
+ msg.kind === 'rpc-reply' &&
34
+ typeof msg.rpcId === 'number');
35
+ }
36
+ /**
37
+ * Create the worker's RPC client over the live IPC channel. Installs the single
38
+ * reply listener; `send` posts requests on the `progress` arm. The caller passes
39
+ * `process` so unit tests can supply a fake duplex.
40
+ */
41
+ export function createWorkerRpcClient(channel) {
42
+ const pending = new Map();
43
+ let nextId = 1;
44
+ const onMessage = (msg) => {
45
+ if (!isRpcReply(msg))
46
+ return; // not a reply — the channel is otherwise outbound
47
+ const waiter = pending.get(msg.rpcId);
48
+ if (waiter === undefined)
49
+ return; // unknown/duplicate rpcId — defensively ignore
50
+ pending.delete(msg.rpcId);
51
+ if (msg.ok)
52
+ waiter.resolve(msg.value);
53
+ else
54
+ waiter.reject(rpcError(msg.error));
55
+ };
56
+ channel.on('message', onMessage);
57
+ return {
58
+ call: (request) => new Promise((resolve, reject) => {
59
+ if (channel.send === undefined) {
60
+ reject(new Error('tool command worker: no IPC channel to issue a host-RPC upcall ' +
61
+ `('${request.seam}') — the worker must be forked with an 'ipc' stdio channel.`));
62
+ return;
63
+ }
64
+ const rpcId = nextId++;
65
+ pending.set(rpcId, { resolve, reject });
66
+ const event = { ...request, rpcId };
67
+ // Call through `channel.send(...)` (not an extracted reference) so the
68
+ // method keeps its `this` binding — `process.send` reads `this.connected`
69
+ // internally and throws if invoked unbound.
70
+ channel.send({ kind: 'progress', event });
71
+ }),
72
+ dispose: () => {
73
+ channel.off?.('message', onMessage);
74
+ pending.clear();
75
+ },
76
+ };
77
+ }
78
+ //# sourceMappingURL=tool-command-worker-rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-command-worker-rpc.js","sourceRoot":"","sources":["../../src/bootstrap/tool-command-worker-rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAgCH,oFAAoF;AACpF,SAAS,QAAQ,CAAC,MAA0D;IAC1E,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAA8B,CAAC;IACnE,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACtD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IACzD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6DAA6D;AAC7D,SAAS,UAAU,CAAC,GAAY;IAC9B,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACX,GAA0B,CAAC,IAAI,KAAK,WAAW;QAChD,OAAQ,GAA2B,CAAC,KAAK,KAAK,QAAQ,CACvD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAIrC;IACC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,MAAM,SAAS,GAAG,CAAC,GAAY,EAAQ,EAAE;QACvC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,kDAAkD;QAChF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,+CAA+C;QACjF,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,EAAE;YAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;;YACjC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAEjC,OAAO;QACL,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAChB,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACvC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,CACJ,IAAI,KAAK,CACP,iEAAiE;oBAC/D,KAAK,OAAO,CAAC,IAAI,6DAA6D,CACjF,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACxC,MAAM,KAAK,GAAmB,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC;YACpD,uEAAuE;YACvE,0EAA0E;YAC1E,4CAA4C;YAC5C,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC;QACJ,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACpC,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * tool-provenance — the shared per-run provenance classifier + the host/worker
3
+ * hook-execution gate (ADR-0054 M4-F).
4
+ *
5
+ * Three sites resolve a tool's `ToolProvenance.source` from the per-run
6
+ * provenance array (recorded by the bootstrap, matched by stable id then human
7
+ * name): `bind-external-dispatch.ts` (dispatch gate), `config-and-capabilities.ts`
8
+ * (config two-pass fold + capability wiring), and now the lifecycle host loops
9
+ * (contributeScope / initialize / report / replay). This module is the ONE matcher
10
+ * so they never drift.
11
+ *
12
+ * It also owns the **host-vs-worker** gate that makes the M4-F lifecycle skip
13
+ * correct. M4-F stops the HOST from executing external-provenance lifecycle +
14
+ * capability hooks — but the dispatch WORKER re-runs the SAME bootstrap code
15
+ * (`build-per-run-scope` / `config-and-capabilities`) and MUST run the dispatched
16
+ * external tool's hooks there (the worker is the legitimate isolation boundary —
17
+ * that is the whole point: external runtime executes in the worker, not the host).
18
+ *
19
+ * So the skip is gated on "this process is the HOST, not a dispatch worker." The
20
+ * supervisor sets {@link IN_TOOL_WORKER_ENV} on the forked child's env; inside the
21
+ * worker {@link isExternalHookHostSkipActive} returns `false`, so the worker runs
22
+ * ALL its registered tools' hooks worker-local exactly as a normal in-process
23
+ * bootstrap would. In the host it returns `true`, so external hooks are skipped.
24
+ */
25
+ import { type Tool, type ToolProvenance, type ToolSource } from '@opensip-cli/core';
26
+ /**
27
+ * Env flag the dispatch supervisor sets on the forked `__tool-command-worker`
28
+ * child so the worker can tell it is the isolation boundary (NOT the host). The
29
+ * worker bootstrap runs the SAME lifecycle loops as the host; this flag disables
30
+ * the M4-F external-hook host-skip inside the worker so the dispatched tool's
31
+ * hooks run there. A separate process + env channel (symmetric to OPENSIP_RUN_ID
32
+ * injection) keeps it deterministic and unit-testable.
33
+ */
34
+ export declare const IN_TOOL_WORKER_ENV = "OPENSIP_CLI_IN_TOOL_WORKER";
35
+ /**
36
+ * Is the M4-F external-hook host-skip ACTIVE in this process? `true` in the host
37
+ * (skip external hooks), `false` inside a dispatch worker (run them — the worker
38
+ * is the isolation boundary). Reads the injected env (defaults to `process.env`)
39
+ * so tests can flip it deterministically without a fork.
40
+ */
41
+ export declare function isExternalHookHostSkipActive(env?: NodeJS.ProcessEnv): boolean;
42
+ /**
43
+ * Must this process REFUSE to import external tool runtimes (ADR-0054 M4-G
44
+ * capstone)? `true` in the HOST — the host registers a manifest-derived synthetic
45
+ * Tool for external provenance and NEVER loads its runtime; `false` inside a
46
+ * dispatch worker (`OPENSIP_CLI_IN_TOOL_WORKER=1`) — the worker IS the isolation
47
+ * boundary, where the untrusted external runtime legitimately loads + runs.
48
+ *
49
+ * This is the same host-vs-worker hinge as the M4-F lifecycle gate (the worker
50
+ * re-runs the SAME bootstrap, so discovery must branch on which process it is),
51
+ * named for the capstone's discovery decision. Reads the injected env (defaults
52
+ * to `process.env`) so tests flip it deterministically without a fork.
53
+ */
54
+ export declare function isHostRuntimeImportForbidden(env?: NodeJS.ProcessEnv): boolean;
55
+ /**
56
+ * Resolve a tool's provenance source from the per-run provenance array. Mirrors
57
+ * the dispatch/config matchers: prefer the stable-id match (UUID → `metadata.id`),
58
+ * fall back to the human key (`ToolProvenance.id` → `metadata.name`). A tool with
59
+ * NO recorded provenance is the trusted/unknown path → `'bundled'` semantics (its
60
+ * hooks run in-host, exactly as before M4-F).
61
+ */
62
+ export declare function provenanceSourceFor(tool: Tool, provenance: readonly ToolProvenance[]): ToolSource;
63
+ /** Find the full provenance record for a tool (stable-id then human-name match). */
64
+ export declare function provenanceRecordFor(tool: Tool, provenance: readonly ToolProvenance[]): ToolProvenance | undefined;
65
+ /**
66
+ * Is this tool external-provenance (installed / project-local / user-global)? A
67
+ * tool with no recorded provenance is treated as bundled (trusted/unknown path).
68
+ */
69
+ export declare function isExternalToolProvenance(tool: Tool, provenance: readonly ToolProvenance[]): boolean;
70
+ /**
71
+ * The M4-F gate: should this tool's lifecycle/capability hook run IN THE HOST
72
+ * process this invocation?
73
+ *
74
+ * - Bundled (or no provenance recorded) → `true` (trusted computing base; runs
75
+ * in-host exactly as before M4-F).
76
+ * - External, host-skip ACTIVE (we are the host) → `false` (the host never
77
+ * executes external runtime hooks; they run worker-side).
78
+ * - External, host-skip INACTIVE (we are inside a dispatch worker) → `true`
79
+ * (the worker IS the isolation boundary — run the dispatched tool's hooks
80
+ * worker-local).
81
+ *
82
+ * @param env Injected env (defaults to `process.env`) for the worker-flag read.
83
+ */
84
+ export declare function shouldRunHookInHost(tool: Tool, provenance: readonly ToolProvenance[], env?: NodeJS.ProcessEnv): boolean;
85
+ //# sourceMappingURL=tool-provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-provenance.d.ts","sourceRoot":"","sources":["../../src/bootstrap/tool-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,+BAA+B,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE1F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,UAAU,CAKjG;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,SAAS,cAAc,EAAE,GACpC,cAAc,GAAG,SAAS,CAK5B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,SAAS,cAAc,EAAE,GACpC,OAAO,CAET;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,SAAS,cAAc,EAAE,EACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAGT"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * tool-provenance — the shared per-run provenance classifier + the host/worker
3
+ * hook-execution gate (ADR-0054 M4-F).
4
+ *
5
+ * Three sites resolve a tool's `ToolProvenance.source` from the per-run
6
+ * provenance array (recorded by the bootstrap, matched by stable id then human
7
+ * name): `bind-external-dispatch.ts` (dispatch gate), `config-and-capabilities.ts`
8
+ * (config two-pass fold + capability wiring), and now the lifecycle host loops
9
+ * (contributeScope / initialize / report / replay). This module is the ONE matcher
10
+ * so they never drift.
11
+ *
12
+ * It also owns the **host-vs-worker** gate that makes the M4-F lifecycle skip
13
+ * correct. M4-F stops the HOST from executing external-provenance lifecycle +
14
+ * capability hooks — but the dispatch WORKER re-runs the SAME bootstrap code
15
+ * (`build-per-run-scope` / `config-and-capabilities`) and MUST run the dispatched
16
+ * external tool's hooks there (the worker is the legitimate isolation boundary —
17
+ * that is the whole point: external runtime executes in the worker, not the host).
18
+ *
19
+ * So the skip is gated on "this process is the HOST, not a dispatch worker." The
20
+ * supervisor sets {@link IN_TOOL_WORKER_ENV} on the forked child's env; inside the
21
+ * worker {@link isExternalHookHostSkipActive} returns `false`, so the worker runs
22
+ * ALL its registered tools' hooks worker-local exactly as a normal in-process
23
+ * bootstrap would. In the host it returns `true`, so external hooks are skipped.
24
+ */
25
+ /**
26
+ * Env flag the dispatch supervisor sets on the forked `__tool-command-worker`
27
+ * child so the worker can tell it is the isolation boundary (NOT the host). The
28
+ * worker bootstrap runs the SAME lifecycle loops as the host; this flag disables
29
+ * the M4-F external-hook host-skip inside the worker so the dispatched tool's
30
+ * hooks run there. A separate process + env channel (symmetric to OPENSIP_RUN_ID
31
+ * injection) keeps it deterministic and unit-testable.
32
+ */
33
+ export const IN_TOOL_WORKER_ENV = 'OPENSIP_CLI_IN_TOOL_WORKER';
34
+ /**
35
+ * Is the M4-F external-hook host-skip ACTIVE in this process? `true` in the host
36
+ * (skip external hooks), `false` inside a dispatch worker (run them — the worker
37
+ * is the isolation boundary). Reads the injected env (defaults to `process.env`)
38
+ * so tests can flip it deterministically without a fork.
39
+ */
40
+ export function isExternalHookHostSkipActive(env = process.env) {
41
+ return env[IN_TOOL_WORKER_ENV] !== '1';
42
+ }
43
+ /**
44
+ * Must this process REFUSE to import external tool runtimes (ADR-0054 M4-G
45
+ * capstone)? `true` in the HOST — the host registers a manifest-derived synthetic
46
+ * Tool for external provenance and NEVER loads its runtime; `false` inside a
47
+ * dispatch worker (`OPENSIP_CLI_IN_TOOL_WORKER=1`) — the worker IS the isolation
48
+ * boundary, where the untrusted external runtime legitimately loads + runs.
49
+ *
50
+ * This is the same host-vs-worker hinge as the M4-F lifecycle gate (the worker
51
+ * re-runs the SAME bootstrap, so discovery must branch on which process it is),
52
+ * named for the capstone's discovery decision. Reads the injected env (defaults
53
+ * to `process.env`) so tests flip it deterministically without a fork.
54
+ */
55
+ export function isHostRuntimeImportForbidden(env = process.env) {
56
+ return isExternalHookHostSkipActive(env);
57
+ }
58
+ /**
59
+ * Resolve a tool's provenance source from the per-run provenance array. Mirrors
60
+ * the dispatch/config matchers: prefer the stable-id match (UUID → `metadata.id`),
61
+ * fall back to the human key (`ToolProvenance.id` → `metadata.name`). A tool with
62
+ * NO recorded provenance is the trusted/unknown path → `'bundled'` semantics (its
63
+ * hooks run in-host, exactly as before M4-F).
64
+ */
65
+ export function provenanceSourceFor(tool, provenance) {
66
+ const recorded = provenance.find((p) => p.stableId !== undefined && p.stableId === tool.metadata.id) ??
67
+ provenance.find((p) => p.id === tool.metadata.name);
68
+ return recorded?.source ?? 'bundled';
69
+ }
70
+ /** Find the full provenance record for a tool (stable-id then human-name match). */
71
+ export function provenanceRecordFor(tool, provenance) {
72
+ return (provenance.find((p) => p.stableId !== undefined && p.stableId === tool.metadata.id) ??
73
+ provenance.find((p) => p.id === tool.metadata.name));
74
+ }
75
+ /**
76
+ * Is this tool external-provenance (installed / project-local / user-global)? A
77
+ * tool with no recorded provenance is treated as bundled (trusted/unknown path).
78
+ */
79
+ export function isExternalToolProvenance(tool, provenance) {
80
+ return provenanceSourceFor(tool, provenance) !== 'bundled';
81
+ }
82
+ /**
83
+ * The M4-F gate: should this tool's lifecycle/capability hook run IN THE HOST
84
+ * process this invocation?
85
+ *
86
+ * - Bundled (or no provenance recorded) → `true` (trusted computing base; runs
87
+ * in-host exactly as before M4-F).
88
+ * - External, host-skip ACTIVE (we are the host) → `false` (the host never
89
+ * executes external runtime hooks; they run worker-side).
90
+ * - External, host-skip INACTIVE (we are inside a dispatch worker) → `true`
91
+ * (the worker IS the isolation boundary — run the dispatched tool's hooks
92
+ * worker-local).
93
+ *
94
+ * @param env Injected env (defaults to `process.env`) for the worker-flag read.
95
+ */
96
+ export function shouldRunHookInHost(tool, provenance, env = process.env) {
97
+ if (!isExternalToolProvenance(tool, provenance))
98
+ return true;
99
+ return !isExternalHookHostSkipActive(env);
100
+ }
101
+ //# sourceMappingURL=tool-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-provenance.js","sourceRoot":"","sources":["../../src/bootstrap/tool-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,4BAA4B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC/E,OAAO,GAAG,CAAC,kBAAkB,CAAC,KAAK,GAAG,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC/E,OAAO,4BAA4B,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAU,EAAE,UAAqC;IACnF,MAAM,QAAQ,GACZ,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnF,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,QAAQ,EAAE,MAAM,IAAI,SAAS,CAAC;AACvC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CACjC,IAAU,EACV,UAAqC;IAErC,OAAO,CACL,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnF,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CACpD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAU,EACV,UAAqC;IAErC,OAAO,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,SAAS,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAU,EACV,UAAqC,EACrC,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,CAAC,wBAAwB,CAAC,IAAI,EAAE,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7D,OAAO,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC;AAC5C,CAAC"}