@descryy/runtime-orchestrator 0.0.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 (49) hide show
  1. package/dist/attach-to-running-jvm-process.d.ts +139 -0
  2. package/dist/attach-to-running-jvm-process.d.ts.map +1 -0
  3. package/dist/attach-to-running-jvm-process.js +338 -0
  4. package/dist/attach-to-running-jvm-process.js.map +1 -0
  5. package/dist/attach-to-running-node-process.d.ts +138 -0
  6. package/dist/attach-to-running-node-process.d.ts.map +1 -0
  7. package/dist/attach-to-running-node-process.js +315 -0
  8. package/dist/attach-to-running-node-process.js.map +1 -0
  9. package/dist/attach-to-running-process.d.ts +70 -0
  10. package/dist/attach-to-running-process.d.ts.map +1 -0
  11. package/dist/attach-to-running-process.js +90 -0
  12. package/dist/attach-to-running-process.js.map +1 -0
  13. package/dist/cdp-client.d.ts +37 -0
  14. package/dist/cdp-client.d.ts.map +1 -0
  15. package/dist/cdp-client.js +111 -0
  16. package/dist/cdp-client.js.map +1 -0
  17. package/dist/cgroup-partial-restriction.d.ts +127 -0
  18. package/dist/cgroup-partial-restriction.d.ts.map +1 -0
  19. package/dist/cgroup-partial-restriction.js +236 -0
  20. package/dist/cgroup-partial-restriction.js.map +1 -0
  21. package/dist/collector-version.d.ts +12 -0
  22. package/dist/collector-version.d.ts.map +1 -0
  23. package/dist/collector-version.js +14 -0
  24. package/dist/collector-version.js.map +1 -0
  25. package/dist/index.d.ts +19 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +10 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/instrumented-execution.d.ts +111 -0
  30. package/dist/instrumented-execution.d.ts.map +1 -0
  31. package/dist/instrumented-execution.js +363 -0
  32. package/dist/instrumented-execution.js.map +1 -0
  33. package/dist/jvm-agent/build.d.ts +72 -0
  34. package/dist/jvm-agent/build.d.ts.map +1 -0
  35. package/dist/jvm-agent/build.js +156 -0
  36. package/dist/jvm-agent/build.js.map +1 -0
  37. package/dist/polling-output-source.d.ts +28 -0
  38. package/dist/polling-output-source.d.ts.map +1 -0
  39. package/dist/polling-output-source.js +73 -0
  40. package/dist/polling-output-source.js.map +1 -0
  41. package/dist/profile-backend-observation.d.ts +94 -0
  42. package/dist/profile-backend-observation.d.ts.map +1 -0
  43. package/dist/profile-backend-observation.js +136 -0
  44. package/dist/profile-backend-observation.js.map +1 -0
  45. package/dist/respawn-and-supervise.d.ts +119 -0
  46. package/dist/respawn-and-supervise.d.ts.map +1 -0
  47. package/dist/respawn-and-supervise.js +212 -0
  48. package/dist/respawn-and-supervise.js.map +1 -0
  49. package/package.json +45 -0
@@ -0,0 +1,363 @@
1
+ /**
2
+ * **The assembly nothing else performed.**
3
+ *
4
+ * Every piece of the runtime existed and none of them were wired together
5
+ * in production code: the controller spawned services, `RuntimeAdapter`
6
+ * could build collectors, `resolveServiceRootForOrigin` could map an origin
7
+ * to a source root, and `EvidenceStore` could persist. Only tests ever
8
+ * connected them, each in its own way, which meant the thing the product
9
+ * actually does had no single implementation to be right or wrong.
10
+ *
11
+ * This module is that implementation. It:
12
+ *
13
+ * 1. runs an `ExecutionController` over a `services` configuration;
14
+ * 2. wraps each spawned process in a **live** `ProcessOutputSource` that
15
+ * polls the accumulated buffer and yields each line exactly once;
16
+ * 3. builds a `CollectorContext` carrying a real `resolveSourceRoot`,
17
+ * closed over the execution's own processes — the channel RT-033 added
18
+ * and RT-029 built the lookup for, which until now nothing populated;
19
+ * 4. starts the adapter's collectors, drains them, stops them;
20
+ * 5. writes every emitted item through `EvidenceStore`, so redaction runs
21
+ * rather than being assumed (plan §28).
22
+ *
23
+ * **What it deliberately does not do.** It does not decide whether a
24
+ * finding exists, does not correlate to graph nodes, and does not drive a
25
+ * browser. Those are separate stages with separate contracts, and folding
26
+ * them in here would make the one place that assembles the runtime also the
27
+ * place that judges it.
28
+ */
29
+ import { resolveServiceRootForOrigin } from "@descryy/runtime-contracts";
30
+ import { createInboundProxy } from "@descryy/runtime-backend-observation";
31
+ import { ExecutionController, checkDependencyVersions, checkEnvironmentVersion, } from "@descryy/runtime-controller";
32
+ import { EvidenceStore } from "@descryy/runtime-evidence-store";
33
+ import { createPollingProcessOutputSource } from "./polling-output-source.js";
34
+ import { COLLECTOR_VERSION } from "./collector-version.js";
35
+ /**
36
+ * Builds the origin → source-root lookup a collector reads through
37
+ * `CollectorContext.resolveSourceRoot`.
38
+ *
39
+ * This is the piece that had been missing at both ends: RT-029 wrote the
40
+ * lookup and nothing called it; RT-033 added the channel and nothing filled
41
+ * it. It closes over the execution's *own* processes, which is what makes
42
+ * the port→service join possible at all — under ephemeral ports the
43
+ * configuration cannot know the port, so only the running process can say.
44
+ */
45
+ export function createSourceRootResolver(processes, configuration) {
46
+ return (origin) => resolveServiceRootForOrigin(origin, { processes, configuration });
47
+ }
48
+ export async function runInstrumentedExecution(input) {
49
+ const configuration = input.execution.configuration;
50
+ const controller = ExecutionController.create(input.execution);
51
+ // Closes §34's remaining "runtime self-observability" gap after RT-219:
52
+ // `controller.run()` has exactly one throw path (`controller.ts`'s own
53
+ // `spawnProcess` catch/rethrow, after it has already recorded the
54
+ // failure in `serviceStartResults` and transitioned to `FAILED_START`) --
55
+ // left unguarded here, that throw used to reject this whole function,
56
+ // uncaught, the identical "harness machinery breaks, zero evidence
57
+ // trail" shape RT-219 already fixed one layer up for `collector.start()`.
58
+ // Caught the same way: written through the same `EvidenceStore`, tagged
59
+ // `source: "harness"`. `controller.execution` and
60
+ // `controller.serviceStartResults` are both real at this point (`run()`
61
+ // mutates its private state, including a call to its own `#finish()`,
62
+ // before it ever throws) so the run resolves with what actually happened
63
+ // rather than rejecting the caller's own promise -- the same "resolves
64
+ // normally" contract RT-219 already established.
65
+ let execution;
66
+ try {
67
+ execution = await controller.run(input.runOptions);
68
+ }
69
+ catch (error) {
70
+ const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
71
+ const harnessEvidence = input.store.write({
72
+ executionId: controller.execution.executionId,
73
+ timestamp: new Date().toISOString(),
74
+ source: "harness",
75
+ service: null,
76
+ process: null,
77
+ eventType: "COLLECTOR_ERROR",
78
+ payload: {
79
+ raw: `ExecutionController.run() threw instead of resolving: ${detail}`,
80
+ error: detail,
81
+ },
82
+ traceId: null,
83
+ requestId: null,
84
+ correlationId: null,
85
+ graphNodeId: null,
86
+ sourceLocation: null,
87
+ stackTrace: null,
88
+ confidence: 1,
89
+ collectorVersion: COLLECTOR_VERSION,
90
+ });
91
+ return {
92
+ execution: controller.execution,
93
+ evidence: [harnessEvidence],
94
+ collectorCapabilities: [],
95
+ validationError: `ExecutionController.run() threw: ${detail}`,
96
+ };
97
+ }
98
+ if (controller.validationError !== null) {
99
+ return { execution, evidence: [], collectorCapabilities: [], validationError: controller.validationError };
100
+ }
101
+ // One source per spawned service. `serviceName` is what joins a handle
102
+ // back to its configuration entry, and a handle without one cannot be
103
+ // attributed -- skipped rather than guessed at.
104
+ // Declared before the sources close over it: each generator polls until
105
+ // this flips, and the flip is what lets `stop()` finish rather than wait
106
+ // forever on a source still watching a live buffer.
107
+ let finished = false;
108
+ const sources = [];
109
+ for (const handle of execution.processes) {
110
+ if (handle.serviceName === null)
111
+ continue;
112
+ const serviceName = handle.serviceName;
113
+ sources.push(createPollingProcessOutputSource({
114
+ processId: handle.processId,
115
+ readOutput: () => controller.readProcessOutput(serviceName),
116
+ isFinished: () => finished,
117
+ }));
118
+ }
119
+ const backendCollectors = input.adapter.createBackendCollectors(sources);
120
+ const emitted = [];
121
+ // `DEC-NEXT-fault-layer-empirical-confirmation.md`'s `dependency`/
122
+ // `environment` mechanisms -- the producer side of the same shape RT-219
123
+ // established for `automation` above: a structural fact the runtime
124
+ // establishes about its own execution environment, tagged
125
+ // `source: "dependency-check"` / `"environment-check"` rather than the
126
+ // service's own domain source, so a reader can tell "the project's
127
+ // declared versions don't match what actually ran" from an observation of
128
+ // the application itself without inspecting payload text. Run once per
129
+ // spawned service (each has its own `cwd`, and therefore potentially its
130
+ // own lockfile/`engines.node`), unconditionally -- "cheap, always
131
+ // available, no rerun needed" per the ruling -- but only ever emitted as
132
+ // evidence when an actual mismatch is found, the same "silence must not
133
+ // manufacture evidence" discipline `"harness"` already observes.
134
+ //
135
+ // The observed Node version is `execution.environmentMetadata`'s own
136
+ // `nodeVersion` -- `process.version` of the controller process, captured
137
+ // once by `captureEnvironmentMetadata` during `controller.run()` above,
138
+ // never re-probed here. It is non-null whenever `validationError` is
139
+ // null (the early return above already excluded the one case where it
140
+ // would not be).
141
+ const observedNodeVersion = execution.environmentMetadata?.nodeVersion ?? process.version;
142
+ for (const handle of execution.processes) {
143
+ if (handle.serviceName === null)
144
+ continue;
145
+ const serviceConfig = configuration.services[handle.serviceName];
146
+ if (serviceConfig === undefined)
147
+ continue;
148
+ const dependencyResult = checkDependencyVersions(serviceConfig.cwd);
149
+ for (const mismatch of dependencyResult.mismatches) {
150
+ emitted.push(input.store.write({
151
+ executionId: execution.executionId,
152
+ timestamp: new Date().toISOString(),
153
+ source: "dependency-check",
154
+ service: handle.serviceName,
155
+ process: handle.processId,
156
+ eventType: "DEPENDENCY_VERSION_MISMATCH",
157
+ payload: {
158
+ raw: `dependency "${mismatch.name}": lockfile declares ${mismatch.declaredVersion}, resolved ${mismatch.resolvedVersion}`,
159
+ name: mismatch.name,
160
+ declaredVersion: mismatch.declaredVersion,
161
+ resolvedVersion: mismatch.resolvedVersion,
162
+ },
163
+ traceId: null,
164
+ requestId: null,
165
+ correlationId: null,
166
+ graphNodeId: null,
167
+ sourceLocation: null,
168
+ stackTrace: null,
169
+ confidence: 1,
170
+ collectorVersion: COLLECTOR_VERSION,
171
+ }));
172
+ }
173
+ const environmentResult = checkEnvironmentVersion(serviceConfig.cwd, observedNodeVersion);
174
+ if (environmentResult.mismatch) {
175
+ emitted.push(input.store.write({
176
+ executionId: execution.executionId,
177
+ timestamp: new Date().toISOString(),
178
+ source: "environment-check",
179
+ service: handle.serviceName,
180
+ process: handle.processId,
181
+ eventType: "ENVIRONMENT_VERSION_MISMATCH",
182
+ payload: {
183
+ raw: `declared node version ${environmentResult.declaredVersion ?? "unknown"} (${environmentResult.declaredSource ?? "unknown"}), observed ${environmentResult.observedVersion}`,
184
+ declaredVersion: environmentResult.declaredVersion,
185
+ declaredSource: environmentResult.declaredSource,
186
+ observedVersion: environmentResult.observedVersion,
187
+ },
188
+ traceId: null,
189
+ requestId: null,
190
+ correlationId: null,
191
+ graphNodeId: null,
192
+ sourceLocation: null,
193
+ stackTrace: null,
194
+ confidence: 1,
195
+ collectorVersion: COLLECTOR_VERSION,
196
+ }));
197
+ }
198
+ }
199
+ // Opt-in InboundProxy wiring (RT-192, DEC-271) -- see `frontWithInboundProxy`'s
200
+ // own doc for what this does and, just as important, what it does not
201
+ // close. Built AFTER `controller.run()` has already returned, so every
202
+ // service's own readiness answer already exists in
203
+ // `controller.serviceStartResults` -- this loop reads that answer, it
204
+ // never re-derives or re-checks it itself.
205
+ const proxies = [];
206
+ if (input.frontWithInboundProxy !== undefined) {
207
+ for (const handle of execution.processes) {
208
+ const serviceName = handle.serviceName;
209
+ if (serviceName === null)
210
+ continue;
211
+ if (input.frontWithInboundProxy[serviceName] !== true)
212
+ continue;
213
+ const readinessOutcome = controller.serviceStartResults.find((result) => result.serviceName === serviceName && result.stage === "readiness");
214
+ if (readinessOutcome?.succeeded !== true || handle.port === null) {
215
+ // Requested but never proven live -- reported as a real
216
+ // COLLECTOR_ERROR, the same shape every other unavailable collector
217
+ // in this function gets, rather than a silent no-proxy fallback
218
+ // ("we did not observe this" vs "there was nothing to observe",
219
+ // plan §17, applies here too).
220
+ emitted.push(input.store.write({
221
+ executionId: execution.executionId,
222
+ timestamp: new Date().toISOString(),
223
+ source: "backend-process",
224
+ service: serviceName,
225
+ process: handle.processId,
226
+ eventType: "COLLECTOR_ERROR",
227
+ payload: {
228
+ raw: `service "${serviceName}" was flagged frontWithInboundProxy but its own readiness never succeeded -- never fronting a process that was not proven live`,
229
+ collectorId: `inbound-proxy:${handle.processId}`,
230
+ },
231
+ traceId: null,
232
+ requestId: null,
233
+ correlationId: null,
234
+ graphNodeId: null,
235
+ sourceLocation: null,
236
+ stackTrace: null,
237
+ confidence: 1,
238
+ collectorVersion: COLLECTOR_VERSION,
239
+ }));
240
+ continue;
241
+ }
242
+ proxies.push({
243
+ serviceName,
244
+ proxy: createInboundProxy({
245
+ targetHost: "127.0.0.1",
246
+ targetPort: handle.port,
247
+ processId: handle.processId,
248
+ service: serviceName,
249
+ }),
250
+ });
251
+ }
252
+ }
253
+ const collectors = [...backendCollectors, ...proxies.map((entry) => entry.proxy.collector)];
254
+ const context = {
255
+ executionId: execution.executionId,
256
+ configuration,
257
+ // Redaction runs here, on the way in -- EvidenceStore.write() is the
258
+ // only path that sets redactionStatus, so nothing reaches the returned
259
+ // array without having been through it (plan §28).
260
+ emit: (evidenceInput) => {
261
+ emitted.push(input.store.write({ ...evidenceInput, executionId: execution.executionId }));
262
+ },
263
+ resolveSourceRoot: createSourceRootResolver(execution.processes, configuration),
264
+ };
265
+ // RT-219 (runtime self-observability). `collector.start()`'s documented
266
+ // failure path resolves `{available: false, reason}` -- the loop below
267
+ // already reports that one, tagged with the domain source the collector
268
+ // was going to observe. A collector that instead THROWS out of start()
269
+ // is a different claim: not "I looked and could not," but the harness's
270
+ // own machinery breaking before it ever got that far. Left unguarded,
271
+ // that throw rejects this Promise.all and `runInstrumentedExecution`
272
+ // aborts uncaught -- zero evidence trail for why, the exact "collector
273
+ // failure mistaken for absence of failure" shape plan §17 forbids,
274
+ // applied to the harness's own code instead of the application's.
275
+ // Caught here and written through the same EvidenceStore, tagged
276
+ // `source: "harness"` rather than the collector's domain source, so a
277
+ // reader can tell "our own observer broke" from "the observed system
278
+ // did" without inspecting payload text.
279
+ const startOutcomes = await Promise.all(collectors.map(async (collector) => {
280
+ try {
281
+ return { collector, result: await collector.start(context), threw: false };
282
+ }
283
+ catch (error) {
284
+ const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
285
+ emitted.push(input.store.write({
286
+ executionId: execution.executionId,
287
+ timestamp: new Date().toISOString(),
288
+ source: "harness",
289
+ service: null,
290
+ process: null,
291
+ eventType: "COLLECTOR_ERROR",
292
+ payload: {
293
+ raw: `collector "${collector.collectorId}" threw during start() instead of resolving {available:false}: ${detail}`,
294
+ collectorId: collector.collectorId,
295
+ error: detail,
296
+ },
297
+ traceId: null,
298
+ requestId: null,
299
+ correlationId: null,
300
+ graphNodeId: null,
301
+ sourceLocation: null,
302
+ stackTrace: null,
303
+ confidence: 1,
304
+ collectorVersion: COLLECTOR_VERSION,
305
+ }));
306
+ return { collector, result: { available: false, reason: `start() threw: ${detail}` }, threw: true };
307
+ }
308
+ }));
309
+ // A thrown start() was already reported above, under `source: "harness"`
310
+ // -- excluded here so it is not reported a second time under
311
+ // `source: "backend-process"` by the loop below, which would collide
312
+ // the two tags on what is really one failure.
313
+ const unavailable = startOutcomes.filter((entry) => !entry.threw && !entry.result.available);
314
+ // A collector that could not start is reported, never silently absent:
315
+ // "we did not observe this" and "there was nothing to observe" are
316
+ // different claims (plan §17).
317
+ for (const entry of unavailable) {
318
+ emitted.push(input.store.write({
319
+ executionId: execution.executionId,
320
+ timestamp: new Date().toISOString(),
321
+ source: "backend-process",
322
+ service: null,
323
+ process: null,
324
+ eventType: "COLLECTOR_ERROR",
325
+ payload: {
326
+ raw: `collector "${entry.collector.collectorId}" reported unavailable at start: ${entry.result.available ? "" : (entry.result.reason ?? "no reason given")}`,
327
+ collectorId: entry.collector.collectorId,
328
+ },
329
+ traceId: null,
330
+ requestId: null,
331
+ correlationId: null,
332
+ graphNodeId: null,
333
+ sourceLocation: null,
334
+ stackTrace: null,
335
+ confidence: 1,
336
+ collectorVersion: COLLECTOR_VERSION,
337
+ }));
338
+ }
339
+ // Every fronted proxy that actually started (a collector reported
340
+ // `available: false` above never reaches here -- its port stayed null on
341
+ // that codepath, matching `InboundProxy.port()`'s own contract).
342
+ const proxyPorts = {};
343
+ for (const entry of proxies) {
344
+ const port = entry.proxy.port();
345
+ if (port !== null)
346
+ proxyPorts[entry.serviceName] = port;
347
+ }
348
+ await input.onReady?.({ execution, proxyPorts });
349
+ await new Promise((resolve) => setTimeout(resolve, input.observeForMs));
350
+ // Order matters: the sources must be told the process is done before
351
+ // stop() awaits their drain, or stop() waits forever on a generator
352
+ // still polling a live buffer.
353
+ finished = true;
354
+ await Promise.all(collectors.map((collector) => collector.stop()));
355
+ await controller.stop();
356
+ return {
357
+ execution: controller.execution,
358
+ evidence: emitted,
359
+ collectorCapabilities: collectors.map((collector) => ({ collectorId: collector.collectorId, capabilities: collector.capabilities() })),
360
+ validationError: null,
361
+ };
362
+ }
363
+ //# sourceMappingURL=instrumented-execution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumented-execution.js","sourceRoot":"","sources":["../src/instrumented-execution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Compiles `Agent.java` into `agent.jar` and `Attacher.java` into
3
+ * `Attacher.class`, once, and only when the source is newer than the build
4
+ * output — the same mtime-cache rule `kotlin-server/build.ts` (test fixture
5
+ * side) already uses, applied here to shipped `src/` code instead of a test
6
+ * fixture, because the agent and its launcher are real product code:
7
+ * `attach-to-running-jvm-process.ts` runs the compiled artifacts, it does
8
+ * not compile them itself at call time.
9
+ *
10
+ * **`tools.jar` is JDK-8-only.** `com.sun.tools.attach.VirtualMachine`
11
+ * shipped inside `tools.jar` through JDK 8; from JDK 9 it moved into the
12
+ * `jdk.attach` module and is resolvable on the classpath with no extra jar.
13
+ * `findToolsJarIfPresent` looks for it and returns `null` when absent
14
+ * (JDK 9+, or a JRE with no `tools.jar` at all) — `Attacher.java` is then
15
+ * compiled and later run with no extra classpath entry, which is correct
16
+ * for JDK 9+ but has only been exercised against JDK 8 on this machine (the
17
+ * only JDK installed here); see this module's own decisions-inbox entry for
18
+ * that disclosed gap.
19
+ *
20
+ * **ByteBuddy, vendored the same way `junit-platform-console-standalone` is
21
+ * for the JUnit reporter** (`packages/test-runner/reporters/junit/build.mjs`):
22
+ * one pinned version, fetched once from Maven Central, checksum-verified,
23
+ * cached beside the agent's own sources — never a build tool. `Agent.java`
24
+ * needs a real bytecode-manipulation library for `DispatcherServlet`
25
+ * method-entry advice (RT-201); hand-written raw ASM/bytecode patching for
26
+ * that is not realistic, and Maven/Gradle here would mean resolving a
27
+ * dependency tree for one library this single file uses. The plain
28
+ * `net.bytebuddy:byte-buddy` artifact (not `byte-buddy-dep`) is used
29
+ * specifically because it repackages its own ASM under
30
+ * `net.bytebuddy.jar.asm.*` (verified directly against this exact jar,
31
+ * `unzip -l`) — one self-contained jar, no second dependency to vendor.
32
+ * `Class-Path: byte-buddy-<version>.jar` in `MANIFEST.MF` is a **static,
33
+ * version-pinned line** rather than a generated one, so a version bump is a
34
+ * two-line, reviewable diff (URL/digest here, `Class-Path` in the manifest)
35
+ * exactly like `junit-platform-console-standalone`'s. It sits beside
36
+ * `agent.jar` in the same directory as `Agent.java` itself (below) because
37
+ * the JDK resolves an agent JAR's `Class-Path` manifest entries relative to
38
+ * the agent JAR's own location, for both `-javaagent` and
39
+ * `VirtualMachine.loadAgent` — the same resolution `Attacher.java` relies on
40
+ * already existing for `Agent-Class` itself.
41
+ *
42
+ * **`Agent.java`/`Attacher.java`/`MANIFEST.MF` live in `packages/orchestrator/jvm-agent/`,
43
+ * a sibling of `src/` and `dist/`, NOT inside `src/jvm-agent/` alongside
44
+ * this file.** Found the hard way, while proving RT-201 from a package
45
+ * OTHER than this one (`@descry/vertical-slice`'s test, importing through
46
+ * the compiled `@descryy/runtime-orchestrator` package boundary rather than
47
+ * this repo's own `src/`): `tsc` only ever emits `.js`/`.d.ts` for `.ts`
48
+ * sources, so a `.java` file nested inside `src/jvm-agent/` had no
49
+ * counterpart under `dist/jvm-agent/` at all, and this module's own
50
+ * `import.meta.url`-relative resolution pointed at a directory that only
51
+ * existed when running from source. This is the exact same problem
52
+ * `test-runner-collector.ts` already solved for the JUnit/xUnit reporters
53
+ * (its own comment: *"Computed from `import.meta.url` like the other
54
+ * reporter paths rather than imported from `reporters/junit/build.mjs`:
55
+ * that file is outside this package's `rootDir`"*) — a relative URL that
56
+ * walks OUT of `src/`/`dist/` to a shared sibling resolves identically from
57
+ * either one, since both sit at the same depth under `packages/orchestrator/`.
58
+ * Applied here the same way, one level up from that file's `reporters/`.
59
+ */
60
+ export declare const JVM_AGENT_DIR: string;
61
+ export declare const BYTEBUDDY_JAR: string;
62
+ /** Fetches the pinned ByteBuddy jar if absent or its checksum no longer matches — refuses a mismatch rather than proceeding, since this jar is both compiled against and loaded into a target JVM. */
63
+ export declare function ensureByteBuddyJar(): Promise<string>;
64
+ /** `$JAVA_HOME/lib/tools.jar` if it exists, else derived from `java`'s own reported `java.home`. `null` when neither yields a real file — the JDK 9+, no-`tools.jar` case. */
65
+ export declare function findToolsJarIfPresent(): string | null;
66
+ /** Compiles `Agent.java` and packages it as `agent.jar` with `Agent-Class` in its manifest. Returns the jar's path. */
67
+ export declare function buildJvmAgentJar(): Promise<string>;
68
+ /** Compiles `Attacher.java` to `Attacher.class` in this same directory. Returns the directory to put on the classpath (the class itself, not a jar — one file, no packaging step needed). */
69
+ export declare function buildAttacherClass(): string;
70
+ /** Ensures a directory exists (for the caller's tmp output file), mirroring the small helper every attach module in this package already needs once for its own scratch state. */
71
+ export declare function ensureDir(path: string): void;
72
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/jvm-agent/build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAQH,eAAO,MAAM,aAAa,QAA6D,CAAC;AAMxF,eAAO,MAAM,aAAa,QAA6D,CAAC;AAMxF,sMAAsM;AACtM,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAe1D;AAMD,8KAA8K;AAC9K,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CAYrD;AAED,uHAAuH;AACvH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CA0BxD;AAED,6LAA6L;AAC7L,wBAAgB,kBAAkB,IAAI,MAAM,CAY3C;AAED,kLAAkL;AAClL,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5C"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Compiles `Agent.java` into `agent.jar` and `Attacher.java` into
3
+ * `Attacher.class`, once, and only when the source is newer than the build
4
+ * output — the same mtime-cache rule `kotlin-server/build.ts` (test fixture
5
+ * side) already uses, applied here to shipped `src/` code instead of a test
6
+ * fixture, because the agent and its launcher are real product code:
7
+ * `attach-to-running-jvm-process.ts` runs the compiled artifacts, it does
8
+ * not compile them itself at call time.
9
+ *
10
+ * **`tools.jar` is JDK-8-only.** `com.sun.tools.attach.VirtualMachine`
11
+ * shipped inside `tools.jar` through JDK 8; from JDK 9 it moved into the
12
+ * `jdk.attach` module and is resolvable on the classpath with no extra jar.
13
+ * `findToolsJarIfPresent` looks for it and returns `null` when absent
14
+ * (JDK 9+, or a JRE with no `tools.jar` at all) — `Attacher.java` is then
15
+ * compiled and later run with no extra classpath entry, which is correct
16
+ * for JDK 9+ but has only been exercised against JDK 8 on this machine (the
17
+ * only JDK installed here); see this module's own decisions-inbox entry for
18
+ * that disclosed gap.
19
+ *
20
+ * **ByteBuddy, vendored the same way `junit-platform-console-standalone` is
21
+ * for the JUnit reporter** (`packages/test-runner/reporters/junit/build.mjs`):
22
+ * one pinned version, fetched once from Maven Central, checksum-verified,
23
+ * cached beside the agent's own sources — never a build tool. `Agent.java`
24
+ * needs a real bytecode-manipulation library for `DispatcherServlet`
25
+ * method-entry advice (RT-201); hand-written raw ASM/bytecode patching for
26
+ * that is not realistic, and Maven/Gradle here would mean resolving a
27
+ * dependency tree for one library this single file uses. The plain
28
+ * `net.bytebuddy:byte-buddy` artifact (not `byte-buddy-dep`) is used
29
+ * specifically because it repackages its own ASM under
30
+ * `net.bytebuddy.jar.asm.*` (verified directly against this exact jar,
31
+ * `unzip -l`) — one self-contained jar, no second dependency to vendor.
32
+ * `Class-Path: byte-buddy-<version>.jar` in `MANIFEST.MF` is a **static,
33
+ * version-pinned line** rather than a generated one, so a version bump is a
34
+ * two-line, reviewable diff (URL/digest here, `Class-Path` in the manifest)
35
+ * exactly like `junit-platform-console-standalone`'s. It sits beside
36
+ * `agent.jar` in the same directory as `Agent.java` itself (below) because
37
+ * the JDK resolves an agent JAR's `Class-Path` manifest entries relative to
38
+ * the agent JAR's own location, for both `-javaagent` and
39
+ * `VirtualMachine.loadAgent` — the same resolution `Attacher.java` relies on
40
+ * already existing for `Agent-Class` itself.
41
+ *
42
+ * **`Agent.java`/`Attacher.java`/`MANIFEST.MF` live in `packages/orchestrator/jvm-agent/`,
43
+ * a sibling of `src/` and `dist/`, NOT inside `src/jvm-agent/` alongside
44
+ * this file.** Found the hard way, while proving RT-201 from a package
45
+ * OTHER than this one (`@descry/vertical-slice`'s test, importing through
46
+ * the compiled `@descryy/runtime-orchestrator` package boundary rather than
47
+ * this repo's own `src/`): `tsc` only ever emits `.js`/`.d.ts` for `.ts`
48
+ * sources, so a `.java` file nested inside `src/jvm-agent/` had no
49
+ * counterpart under `dist/jvm-agent/` at all, and this module's own
50
+ * `import.meta.url`-relative resolution pointed at a directory that only
51
+ * existed when running from source. This is the exact same problem
52
+ * `test-runner-collector.ts` already solved for the JUnit/xUnit reporters
53
+ * (its own comment: *"Computed from `import.meta.url` like the other
54
+ * reporter paths rather than imported from `reporters/junit/build.mjs`:
55
+ * that file is outside this package's `rootDir`"*) — a relative URL that
56
+ * walks OUT of `src/`/`dist/` to a shared sibling resolves identically from
57
+ * either one, since both sit at the same depth under `packages/orchestrator/`.
58
+ * Applied here the same way, one level up from that file's `reporters/`.
59
+ */
60
+ import { spawnSync } from "node:child_process";
61
+ import { createHash } from "node:crypto";
62
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
63
+ import { join } from "node:path";
64
+ import { fileURLToPath } from "node:url";
65
+ export const JVM_AGENT_DIR = fileURLToPath(new URL("../../jvm-agent", import.meta.url));
66
+ /** Pinned. A version bump changes the URL, the digest, and `MANIFEST.MF`'s `Class-Path` line, in one reviewable diff. */
67
+ const BYTEBUDDY_VERSION = "1.15.11";
68
+ const BYTEBUDDY_SHA256 = "fa08998aae1e7bdae83bde0712c50e8444d71c0e0c196bb2247ade8d4ad0eb90";
69
+ const BYTEBUDDY_URL = `https://repo1.maven.org/maven2/net/bytebuddy/byte-buddy/${BYTEBUDDY_VERSION}/byte-buddy-${BYTEBUDDY_VERSION}.jar`;
70
+ export const BYTEBUDDY_JAR = join(JVM_AGENT_DIR, `byte-buddy-${BYTEBUDDY_VERSION}.jar`);
71
+ function digestOf(path) {
72
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
73
+ }
74
+ /** Fetches the pinned ByteBuddy jar if absent or its checksum no longer matches — refuses a mismatch rather than proceeding, since this jar is both compiled against and loaded into a target JVM. */
75
+ export async function ensureByteBuddyJar() {
76
+ if (existsSync(BYTEBUDDY_JAR) && digestOf(BYTEBUDDY_JAR) === BYTEBUDDY_SHA256)
77
+ return BYTEBUDDY_JAR;
78
+ mkdirSync(JVM_AGENT_DIR, { recursive: true });
79
+ const response = await fetch(BYTEBUDDY_URL);
80
+ if (!response.ok) {
81
+ throw new Error(`jvm-agent build: could not fetch ${BYTEBUDDY_URL} — HTTP ${String(response.status)}`);
82
+ }
83
+ writeFileSync(BYTEBUDDY_JAR, Buffer.from(await response.arrayBuffer()));
84
+ const actual = digestOf(BYTEBUDDY_JAR);
85
+ if (actual !== BYTEBUDDY_SHA256) {
86
+ throw new Error(`jvm-agent build: checksum mismatch for ${BYTEBUDDY_JAR}\n expected ${BYTEBUDDY_SHA256}\n actual ${actual}`);
87
+ }
88
+ return BYTEBUDDY_JAR;
89
+ }
90
+ function newerThan(output, source) {
91
+ return existsSync(output) && statSync(output).mtimeMs >= statSync(source).mtimeMs;
92
+ }
93
+ /** `$JAVA_HOME/lib/tools.jar` if it exists, else derived from `java`'s own reported `java.home`. `null` when neither yields a real file — the JDK 9+, no-`tools.jar` case. */
94
+ export function findToolsJarIfPresent() {
95
+ const fromEnv = process.env["JAVA_HOME"];
96
+ if (fromEnv !== undefined) {
97
+ const candidate = join(fromEnv, "lib", "tools.jar");
98
+ if (existsSync(candidate))
99
+ return candidate;
100
+ }
101
+ const probe = spawnSync("java", ["-XshowSettings:properties", "-version"], { encoding: "utf8" });
102
+ const combined = `${probe.stdout ?? ""}${probe.stderr ?? ""}`;
103
+ const match = /java\.home\s*=\s*(.+)/.exec(combined);
104
+ if (match?.[1] === undefined)
105
+ return null;
106
+ const candidate = join(match[1].trim(), "lib", "tools.jar");
107
+ return existsSync(candidate) ? candidate : null;
108
+ }
109
+ /** Compiles `Agent.java` and packages it as `agent.jar` with `Agent-Class` in its manifest. Returns the jar's path. */
110
+ export async function buildJvmAgentJar() {
111
+ const source = join(JVM_AGENT_DIR, "Agent.java");
112
+ const jarPath = join(JVM_AGENT_DIR, "agent.jar");
113
+ const byteBuddyJar = await ensureByteBuddyJar();
114
+ // Cache invalidates on either input changing -- a ByteBuddy version bump
115
+ // (new jar path/mtime) must also trigger a rebuild, not just an Agent.java
116
+ // edit, since the compiled advice classes are compiled against it.
117
+ if (newerThan(jarPath, source) && newerThan(jarPath, byteBuddyJar))
118
+ return jarPath;
119
+ const compiled = spawnSync("javac", ["-cp", byteBuddyJar, "-d", JVM_AGENT_DIR, source], { encoding: "utf8", timeout: 60_000 });
120
+ if (compiled.status !== 0) {
121
+ throw new Error(`javac failed to compile Agent.java. This is a toolchain failure, not a test failure:\n${compiled.stdout ?? ""}${compiled.stderr ?? ""}`);
122
+ }
123
+ // `Agent.java` now declares nested static classes (the ByteBuddy advice)
124
+ // -- `javac` emits one `Agent$Name.class` per nested class alongside
125
+ // `Agent.class`, and every one of them must be in the jar or the JVM
126
+ // throws NoClassDefFoundError the first time DispatcherServlet is loaded
127
+ // in the target and the advice class is needed.
128
+ const agentClassFiles = readdirSync(JVM_AGENT_DIR).filter((name) => name === "Agent.class" || name.startsWith("Agent$"));
129
+ const jarArgs = ["cfm", jarPath, join(JVM_AGENT_DIR, "MANIFEST.MF")];
130
+ for (const classFile of agentClassFiles)
131
+ jarArgs.push("-C", JVM_AGENT_DIR, classFile);
132
+ const jarred = spawnSync("jar", jarArgs, { encoding: "utf8", timeout: 30_000 });
133
+ if (jarred.status !== 0) {
134
+ throw new Error(`jar failed to package agent.jar:\n${jarred.stdout ?? ""}${jarred.stderr ?? ""}`);
135
+ }
136
+ return jarPath;
137
+ }
138
+ /** Compiles `Attacher.java` to `Attacher.class` in this same directory. Returns the directory to put on the classpath (the class itself, not a jar — one file, no packaging step needed). */
139
+ export function buildAttacherClass() {
140
+ const source = join(JVM_AGENT_DIR, "Attacher.java");
141
+ const classFile = join(JVM_AGENT_DIR, "Attacher.class");
142
+ if (newerThan(classFile, source))
143
+ return JVM_AGENT_DIR;
144
+ const toolsJar = findToolsJarIfPresent();
145
+ const args = toolsJar !== null ? ["-cp", toolsJar, "-d", JVM_AGENT_DIR, source] : ["-d", JVM_AGENT_DIR, source];
146
+ const compiled = spawnSync("javac", args, { encoding: "utf8", timeout: 60_000 });
147
+ if (compiled.status !== 0) {
148
+ throw new Error(`javac failed to compile Attacher.java. This is a toolchain failure, not a test failure:\n${compiled.stdout ?? ""}${compiled.stderr ?? ""}`);
149
+ }
150
+ return JVM_AGENT_DIR;
151
+ }
152
+ /** Ensures a directory exists (for the caller's tmp output file), mirroring the small helper every attach module in this package already needs once for its own scratch state. */
153
+ export function ensureDir(path) {
154
+ mkdirSync(path, { recursive: true });
155
+ }
156
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/jvm-agent/build.ts"],"names":[],"mappings":"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"}