ttsc 0.31.1 → 0.31.2

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 (135) hide show
  1. package/lib/compiler/internal/build/BuildExecution.d.ts +4 -2
  2. package/lib/compiler/internal/build/BuildExecution.js +40 -12
  3. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  4. package/lib/compiler/internal/startResidentTransform.js +5 -4
  5. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  6. package/lib/compiler/internal/transformProjectInMemory.js +9 -7
  7. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  8. package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
  9. package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
  10. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  11. package/lib/internal/pluginSource.d.ts +1 -0
  12. package/lib/internal/pluginSource.js +1 -0
  13. package/lib/internal/pluginSource.js.map +1 -1
  14. package/lib/internal/resolveNodeBinary.d.ts +6 -1
  15. package/lib/internal/resolveNodeBinary.js +6 -2
  16. package/lib/internal/resolveNodeBinary.js.map +1 -1
  17. package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
  18. package/lib/internal/runtimeExecutableIdentity.js +192 -102
  19. package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
  20. package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
  21. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  22. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
  23. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
  24. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
  25. package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
  26. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  27. package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
  28. package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
  29. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  30. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
  31. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
  32. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  33. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -1
  34. package/lib/plugin/internal/load/loadProjectPlugins.js +80 -20
  35. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  36. package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
  37. package/lib/plugin/internal/readCapabilityResolution.js +8 -5
  38. package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
  39. package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +9 -3
  40. package/lib/plugin/internal/source/GoEnvironmentReading.js +53 -7
  41. package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -1
  42. package/lib/plugin/internal/source/NativeSourcePackages.d.ts +44 -6
  43. package/lib/plugin/internal/source/NativeSourcePackages.js +150 -8
  44. package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
  45. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -3
  46. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +32 -7
  47. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  48. package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
  49. package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
  50. package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
  51. package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
  52. package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
  53. package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
  54. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
  55. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
  56. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  57. package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +5 -2
  58. package/lib/plugin/internal/source/SourcePluginWorkspace.js +5 -3
  59. package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
  60. package/lib/plugin/internal/source/buildSourcePlugin.d.ts +15 -2
  61. package/lib/plugin/internal/source/buildSourcePlugin.js +27 -4
  62. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  63. package/lib/plugin/internal/source/computeCacheKey.d.ts +21 -5
  64. package/lib/plugin/internal/source/computeCacheKey.js +71 -29
  65. package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
  66. package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
  67. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +74 -35
  68. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
  69. package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
  70. package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
  71. package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
  72. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
  73. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
  74. package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
  75. package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
  76. package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
  77. package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
  78. package/lib/plugin/internal/source/pluginSourceState.js +20 -8
  79. package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
  80. package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
  81. package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
  82. package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
  83. package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
  84. package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
  85. package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
  86. package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
  87. package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
  88. package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
  89. package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
  90. package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
  91. package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
  92. package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
  93. package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
  94. package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
  95. package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
  96. package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
  97. package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
  98. package/lib/plugin/resolveCapabilityPlugins.js +67 -15
  99. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
  100. package/package.json +8 -8
  101. package/src/compiler/internal/build/BuildExecution.ts +46 -13
  102. package/src/compiler/internal/startResidentTransform.ts +12 -3
  103. package/src/compiler/internal/transformProjectInMemory.ts +10 -2
  104. package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
  105. package/src/internal/pluginSource.ts +1 -0
  106. package/src/internal/resolveNodeBinary.ts +12 -1
  107. package/src/internal/runtimeExecutableIdentity.ts +227 -141
  108. package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
  109. package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
  110. package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
  111. package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
  112. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
  113. package/src/plugin/internal/load/loadProjectPlugins.ts +212 -94
  114. package/src/plugin/internal/readCapabilityResolution.ts +17 -6
  115. package/src/plugin/internal/source/GoEnvironmentReading.ts +74 -5
  116. package/src/plugin/internal/source/NativeSourcePackages.ts +262 -5
  117. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +41 -5
  118. package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
  119. package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
  120. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
  121. package/src/plugin/internal/source/SourcePluginWorkspace.ts +6 -1
  122. package/src/plugin/internal/source/buildSourcePlugin.ts +43 -3
  123. package/src/plugin/internal/source/computeCacheKey.ts +103 -47
  124. package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +144 -29
  125. package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
  126. package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
  127. package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
  128. package/src/plugin/internal/source/pluginSourceState.ts +28 -12
  129. package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
  130. package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
  131. package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
  132. package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
  133. package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
  134. package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
  135. package/src/plugin/resolveCapabilityPlugins.ts +87 -13
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
 
3
3
  import { resolveNodeBinary } from "../../internal/resolveNodeBinary";
4
4
  import { loadProjectPlugins } from "../../plugin/internal/load/loadProjectPlugins";
5
+ import type { PluginContentIdentities } from "../../plugin/internal/source/PluginContentIdentities";
5
6
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
6
7
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
7
8
  import { ResidentTransformProcess } from "./ResidentTransformProcess";
@@ -94,7 +95,13 @@ export function startResidentTransform(
94
95
  ],
95
96
  binary: host.binary,
96
97
  cwd: project.root,
97
- env: residentEnv(context, project.root, tsgoBinary, loaded.nativePlugins),
98
+ env: residentEnv(
99
+ context,
100
+ project.root,
101
+ tsgoBinary,
102
+ loaded.nativePlugins,
103
+ loaded.identities,
104
+ ),
98
105
  });
99
106
  return { process: resident, projectRoot: project.root };
100
107
  }
@@ -106,13 +113,15 @@ export function startResidentTransform(
106
113
  * plugin config anchor (an embedder compiling through a generated wrapper
107
114
  * tsconfig) so config-file discovery walks the real project instead of the
108
115
  * wrapper's temp-dir ancestry, and forwards linked transform plugins via
109
- * `TTSC_LINKED_PLUGINS_JSON`.
116
+ * `TTSC_LINKED_PLUGINS_JSON`. The load's record store proves the Node runtime
117
+ * from metadata instead of streaming it again (#1723).
110
118
  */
111
119
  function residentEnv(
112
120
  context: ITtscCompilerContext,
113
121
  projectRoot: string,
114
122
  tsgoBinary: string,
115
123
  nativePlugins: readonly ITtscLoadedNativePlugin[],
124
+ identities: PluginContentIdentities.Store | undefined,
116
125
  ): NodeJS.ProcessEnv {
117
126
  const pluginConfigDir = resolvePluginConfigDir(context);
118
127
  const env = SidecarEnvironment.merge(
@@ -128,7 +137,7 @@ function residentEnv(
128
137
  context.env,
129
138
  { TTSC_TSGO_BINARY: tsgoBinary },
130
139
  );
131
- const node = resolveNodeBinary(env, projectRoot);
140
+ const node = resolveNodeBinary(env, projectRoot, identities);
132
141
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
133
142
  // The anchor is per-invocation state owned by this host: when this run
134
143
  // declared none (and the caller's env does not name one), drop any value
@@ -5,6 +5,7 @@ import { collectProjectHostInputs } from "../../plugin/internal/load/collectProj
5
5
  import { hashHostInputPaths } from "../../plugin/internal/load/hashHostInputPaths";
6
6
  import { loadProjectPlugins } from "../../plugin/internal/load/loadProjectPlugins";
7
7
  import { realpathHostInputPaths } from "../../plugin/internal/load/realpathHostInputPaths";
8
+ import type { PluginContentIdentities } from "../../plugin/internal/source/PluginContentIdentities";
8
9
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
9
10
  import type { ITtscCompilerTransformation } from "../../structures/ITtscCompilerTransformation";
10
11
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
@@ -301,6 +302,7 @@ function transformProjectWithPlugins(
301
302
  tsgoBinary,
302
303
  loaded.nativePlugins,
303
304
  checks,
305
+ loaded.identities,
304
306
  );
305
307
  const checkedHostInputs = mergeHostInputs(
306
308
  loaded.hostInputs,
@@ -414,6 +416,7 @@ function transformProjectWithPlugins(
414
416
  tsgoBinary,
415
417
  loaded.nativePlugins,
416
418
  plugin,
419
+ loaded.identities,
417
420
  ),
418
421
  },
419
422
  );
@@ -688,6 +691,7 @@ function runNativeChecks(
688
691
  tsgoBinary: string,
689
692
  nativePlugins: readonly ITtscLoadedNativePlugin[],
690
693
  checks: readonly ITtscLoadedNativePlugin[],
694
+ identities: PluginContentIdentities.Store | undefined,
691
695
  ): TtscBuildResult {
692
696
  let result: TtscBuildResult = {
693
697
  diagnostics: [],
@@ -716,6 +720,7 @@ function runNativeChecks(
716
720
  tsgoBinary,
717
721
  nativePlugins,
718
722
  plugin,
723
+ identities,
719
724
  ),
720
725
  },
721
726
  );
@@ -804,7 +809,9 @@ function serializeNativePlugins(
804
809
  * the caller declared a plugin config anchor (an embedder compiling through a
805
810
  * generated wrapper tsconfig) so config-file discovery walks the real project
806
811
  * instead of the wrapper's temp-dir ancestry. For transform plugins, also
807
- * passes `TTSC_LINKED_PLUGINS_JSON` when linked sources are present.
812
+ * passes `TTSC_LINKED_PLUGINS_JSON` when linked sources are present. The load's
813
+ * record store proves the Node runtime from metadata instead of streaming it
814
+ * before every spawn of every generation (#1723).
808
815
  */
809
816
  function nativePluginEnv(
810
817
  options: ITtscCompilerContext,
@@ -812,6 +819,7 @@ function nativePluginEnv(
812
819
  tsgoBinary: string,
813
820
  nativePlugins?: readonly ITtscLoadedNativePlugin[],
814
821
  plugin?: ITtscLoadedNativePlugin,
822
+ identities?: PluginContentIdentities.Store,
815
823
  ): NodeJS.ProcessEnv {
816
824
  const pluginConfigDir = resolvePluginConfigDir(options);
817
825
  const env = SidecarEnvironment.merge(
@@ -827,7 +835,7 @@ function nativePluginEnv(
827
835
  options.env,
828
836
  { TTSC_TSGO_BINARY: tsgoBinary },
829
837
  );
830
- const node = resolveNodeBinary(env, projectRoot);
838
+ const node = resolveNodeBinary(env, projectRoot, identities);
831
839
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
832
840
  // The anchor is per-invocation state owned by this host: when this run
833
841
  // declared none (and the caller's env does not name one), drop any value
@@ -4,6 +4,8 @@ import path from "node:path";
4
4
 
5
5
  import { captureProcessOutput } from "../compiler/internal/captureProcessOutput";
6
6
  import { SidecarEnvironment } from "../compiler/internal/sharedHost/SidecarEnvironment";
7
+ import type { PluginContentIdentities } from "../plugin/internal/source/PluginContentIdentities";
8
+ import { PluginLoadAnswers } from "../plugin/internal/source/PluginLoadAnswers";
7
9
  import { E2ETrace } from "./E2ETrace";
8
10
  import type { IJavaScriptRuntimeCapabilities } from "./IJavaScriptRuntimeCapabilities";
9
11
  import { OwnedSynchronousProcess } from "./OwnedSynchronousProcess";
@@ -20,28 +22,56 @@ import { spawnSyncWithLowDescriptors } from "./spawnSyncWithLowDescriptors";
20
22
  * Cache-ineligible candidates do not acquire executable proofs used only by
21
23
  * cache lookup and publication.
22
24
  *
23
- * @evidence contracts/common.md#principled-implementation A child reports its own feature availability and executable; byte-aware before/after identity and same-executable proof authorize reuse only for a stable absolute runtime without NODE_OPTIONS preload authority.
25
+ * With a record store, the executable identity is proven from metadata and a
26
+ * probe answer recorded for that identity is reused by a later process (#1723);
27
+ * a recorded answer must still name an executable this runtime is.
28
+ *
29
+ * @param runtime The interpreter to probe.
30
+ * @param env Caller environment merged over this process's.
31
+ * @param cwd Working directory of the probe.
32
+ * @param identities Optional record store of the caller's plugin cache.
33
+ * @evidence contracts/common.md#principled-implementation A child reports its own feature availability and executable; byte-aware before/after identity and same-executable proof authorize reuse only for a stable absolute runtime without NODE_OPTIONS preload authority, in process or through an answer recorded under that identity.
24
34
  * @evidence contracts/common.md#clear-and-simple-design One probe owner coordinates identity, spawning and parsing; the shared fingerprint helper owns file-content proof and the low-descriptor helper owns the POSIX launch distinction.
25
35
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Feature availability comes from the actual interpreter rather than its name; descriptor fallback handles a supported kernel constraint and failures produce unsupported capabilities, not fabricated success.
26
36
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain measured capabilities, reuse restrictions and freshness, with comments separating platform launch and cache permission from tags.
27
37
  * @evidence contracts/portability.md#os-neutral-implementation Shared environment merge and lookup apply native name identity to caller precedence and NODE_OPTIONS preload authority, including Windows aliases; Node spawn receives an executable and argv without shell syntax, and only POSIX descriptor exhaustion uses the isolated broker.
28
- * @evidence contracts/performance.md#efficient-algorithms Environment merge/name handling first decides cache eligibility. Eligible hits read B executable bytes; eligible misses acquire before/after content proofs around the actual probe. Ineligible calls retain the fresh child without reading executable bytes solely for the forbidden cache. Probe spawning/parsing, a possible descriptor-exhaustion retry and capture-file reads add text/environment/native lookup costs and transient output storage; this adapter sets no explicit byte quota.
29
- * @evidence contracts/performance.md#reuse-equivalent-work Stable absolute candidates that report their own executable share measured capability results, including false feature flags; changed executable bytes, link/target identity, preload options or wrapper identity require another probe, while failed probes without executable identity are not reused.
38
+ * @evidence contracts/performance.md#efficient-algorithms Environment merge/name handling first decides cache eligibility. Eligible hits prove the executable from a record (three stats and one small read) or read B executable bytes; eligible misses acquire before/after identities around the actual probe. Ineligible calls retain the fresh child without reading executable bytes solely for the forbidden cache. Probe spawning/parsing, a possible descriptor-exhaustion retry and capture-file reads add text/environment/native lookup costs and transient output storage; this adapter sets no explicit byte quota.
39
+ * @evidence contracts/performance.md#reuse-equivalent-work Stable absolute candidates that report their own executable share measured capability results, including false feature flags, within the process and across processes through the record store keyed by the full identity; changed executable bytes, link/target identity, preload options or wrapper identity require another probe, while failed probes without executable identity are not reused.
30
40
  * @evidence contracts/performance.md#bound-retention-and-release-resources The synchronous probe owns child completion and fallback captures; finally attempts capture close/removal, whose helper suppresses cleanup failures, so native/file release is unconfirmed on failure. Capability entries retain absolute spellings and path/identity text with no eviction quota; failed identity validation can remove an entry. Returned capability copies transfer to callers.
31
41
  */
32
42
  export function javascriptRuntimeCapabilities(
33
43
  runtime: string,
34
44
  env: NodeJS.ProcessEnv,
35
45
  cwd: string,
46
+ identities?: PluginContentIdentities.Store,
36
47
  ): IJavaScriptRuntimeCapabilities {
37
48
  const effectiveEnv = SidecarEnvironment.merge(process.env, env);
38
49
  const cacheKey = runtimeCapabilityCacheKey(runtime, effectiveEnv);
39
50
  const beforeIdentity =
40
- cacheKey === undefined ? undefined : runtimeExecutableIdentity(runtime);
51
+ cacheKey === undefined
52
+ ? undefined
53
+ : runtimeExecutableIdentity(runtime, identities);
41
54
  if (cacheKey !== undefined && beforeIdentity !== undefined) {
42
55
  const cached = runtimeCapabilityCache.get(cacheKey);
43
56
  if (cached?.identity === beforeIdentity) return { ...cached.capabilities };
44
57
  runtimeCapabilityCache.delete(cacheKey);
58
+ // WARNING (#1723): a launch is a new process, so the memo above is empty
59
+ // on every CLI run. The same proven executable answers the same probe; the
60
+ // record store carries that answer across processes.
61
+ const recorded = recordedCapabilities(
62
+ PluginLoadAnswers.read(identities, RUNTIME_CAPABILITIES, [
63
+ cacheKey,
64
+ beforeIdentity,
65
+ ]),
66
+ runtime,
67
+ );
68
+ if (recorded !== undefined) {
69
+ runtimeCapabilityCache.set(cacheKey, {
70
+ capabilities: { ...recorded },
71
+ identity: beforeIdentity,
72
+ });
73
+ return recorded;
74
+ }
45
75
  }
46
76
  const args = [
47
77
  "-e",
@@ -113,7 +143,9 @@ export function javascriptRuntimeCapabilities(
113
143
  // This removes a process spawn from the common path without
114
144
  // authorizing a replaced or redirected runtime in a long-lived host.
115
145
  const afterIdentity =
116
- cacheKey === undefined ? undefined : runtimeExecutableIdentity(runtime);
146
+ cacheKey === undefined
147
+ ? undefined
148
+ : runtimeExecutableIdentity(runtime, identities);
117
149
  if (
118
150
  cacheKey !== undefined &&
119
151
  capabilities.executable !== undefined &&
@@ -125,10 +157,44 @@ export function javascriptRuntimeCapabilities(
125
157
  capabilities: { ...capabilities },
126
158
  identity: afterIdentity,
127
159
  });
160
+ PluginLoadAnswers.write(
161
+ identities,
162
+ RUNTIME_CAPABILITIES,
163
+ [cacheKey, afterIdentity],
164
+ capabilities,
165
+ );
128
166
  }
129
167
  return capabilities;
130
168
  }
131
169
 
170
+ /** Answer family of measured runtime capabilities (`PluginLoadAnswers`). */
171
+ const RUNTIME_CAPABILITIES = "javascript-runtime-capabilities";
172
+
173
+ /**
174
+ * A recorded probe answer in the shape the probe produces, still naming an
175
+ * executable this runtime is, or `undefined`.
176
+ */
177
+ function recordedCapabilities(
178
+ value: unknown,
179
+ runtime: string,
180
+ ): IJavaScriptRuntimeCapabilities | undefined {
181
+ if (typeof value !== "object" || value === null) return undefined;
182
+ const recorded = value as Partial<IJavaScriptRuntimeCapabilities>;
183
+ if (
184
+ typeof recorded.bun !== "boolean" ||
185
+ typeof recorded.registerHooks !== "boolean" ||
186
+ typeof recorded.executable !== "string" ||
187
+ !path.isAbsolute(recorded.executable) ||
188
+ !sameRuntimeExecutable(runtime, recorded.executable)
189
+ )
190
+ return undefined;
191
+ return {
192
+ bun: recorded.bun,
193
+ executable: recorded.executable,
194
+ registerHooks: recorded.registerHooks,
195
+ };
196
+ }
197
+
132
198
  type RuntimeCapabilityCacheEntry = {
133
199
  capabilities: IJavaScriptRuntimeCapabilities;
134
200
  identity: string;
@@ -22,5 +22,6 @@ export * from "../plugin/internal/source/pluginSourceDigest";
22
22
  export * from "../plugin/internal/source/pluginSourceState";
23
23
  export * from "../plugin/internal/source/pluginSourceStateHolds";
24
24
  export * from "../plugin/internal/source/processPluginBuildEnvironment";
25
+ export * from "../plugin/internal/source/provenPluginSourceDigest";
25
26
  export * from "../plugin/internal/source/PluginBuildEnvironmentReadings";
26
27
  export * from "../plugin/internal/source/prunesPluginSourceDirectory";
@@ -1,4 +1,5 @@
1
1
  import { SidecarEnvironment } from "../compiler/internal/sharedHost/SidecarEnvironment";
2
+ import type { PluginContentIdentities } from "../plugin/internal/source/PluginContentIdentities";
2
3
  import { javascriptRuntimeCapabilities } from "./javascriptRuntimeCapabilities";
3
4
 
4
5
  /**
@@ -7,6 +8,10 @@ import { javascriptRuntimeCapabilities } from "./javascriptRuntimeCapabilities";
7
8
  * Bun can directly evaluate a descriptor, but it does not implement the
8
9
  * synchronous `module.registerHooks` contract used by those loaders.
9
10
  *
11
+ * @param env Effective environment naming `TTSC_NODE_BINARY`.
12
+ * @param cwd Working directory of the probes.
13
+ * @param identities Optional record store that lets probes of an unchanged
14
+ * runtime be answered across processes (#1723).
10
15
  * @evidence contracts/common.md#principled-implementation Ordered candidates are accepted only after an actual runtime probe reports non-Bun registerHooks support and its absolute executable; names alone do not establish the loader capability.
11
16
  * @evidence contracts/common.md#clear-and-simple-design Candidate precedence and duplicate suppression remain here, while the shared probe owns launch, capability parsing and freshness policy.
12
17
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The node spelling is a supported final discovery candidate, not an assumed successful runtime; incompatible candidates are skipped without patching modules or globals.
@@ -19,6 +24,7 @@ import { javascriptRuntimeCapabilities } from "./javascriptRuntimeCapabilities";
19
24
  export function resolveNodeBinary(
20
25
  env: NodeJS.ProcessEnv = process.env,
21
26
  cwd: string = process.cwd(),
27
+ identities?: PluginContentIdentities.Store,
22
28
  ): string | undefined {
23
29
  const candidates = [
24
30
  SidecarEnvironment.read(env, "TTSC_NODE_BINARY"),
@@ -31,7 +37,12 @@ export function resolveNodeBinary(
31
37
  if (candidate === undefined || candidate.trim() === "") continue;
32
38
  if (seen.has(candidate)) continue;
33
39
  seen.add(candidate);
34
- const capabilities = javascriptRuntimeCapabilities(candidate, env, cwd);
40
+ const capabilities = javascriptRuntimeCapabilities(
41
+ candidate,
42
+ env,
43
+ cwd,
44
+ identities,
45
+ );
35
46
  if (
36
47
  !capabilities.bun &&
37
48
  capabilities.registerHooks &&