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
@@ -22,9 +22,12 @@ import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITts
22
22
  import { pluginDescriptorFailureReason } from "../pluginDescriptorFailureReason";
23
23
  import { pluginDescriptorProcessFailure } from "../pluginDescriptorProcessFailure";
24
24
  import { NativeSourcePackages } from "../source/NativeSourcePackages";
25
+ import type { PluginBuildEnvironmentWitness } from "../source/PluginBuildEnvironmentWitness";
26
+ import { PluginContentIdentities } from "../source/PluginContentIdentities";
25
27
  import { SourcePluginWorkspace } from "../source/SourcePluginWorkspace";
26
28
  import { buildSourcePlugin } from "../source/buildSourcePlugin";
27
29
  import { isPathWithin } from "../source/isPathWithin";
30
+ import { pluginBuildEnvironment } from "../source/pluginBuildEnvironment";
28
31
  import { pluginBuildVersions } from "../source/pluginBuildVersions";
29
32
  import { pluginModuleReplaceDirectories } from "../source/pluginModuleReplaceDirectories";
30
33
  import { pluginSourceState } from "../source/pluginSourceState";
@@ -99,7 +102,7 @@ import { visitImportMappedCandidates } from "./visitImportMappedCandidates";
99
102
  * @evidence contracts/common.md#meaningful-documentation Native JSDoc explains result provenance, environment and path options; helper comments explain input races, conservative proof omission, fallback authority and cleanup. Member/tag spacing and separated concepts follow the documentation skill.
100
103
  * @evidence contracts/portability.md#os-neutral-implementation Native path/file-URL conversion, createRequire and hidden spawn with explicit argv/environment implement OS-neutral selection and process execution. Physical identities are preserved separately from lexical candidates; Bun-specific differences are capability decisions, not OS guesses.
101
104
  * @evidence contracts/performance.md#efficient-algorithms Input merging and conflicts use Sets/maps and sorted path populations, with path/key/value byte costs. Discovery can repeat config/manifest/candidate reads and full hashes; descriptor work includes runtime probes, full observation/JSON processing and possibly synchronous evaluation. Watch-only preliminary publication can temporarily digest/watch the full admitted module as well as its package before kind is known; callbacks delegate topology reconciliation. Load-local replacement readings serve preliminary and final publication. Package selection groups equivalent module contexts and batches entries through Go list without dependency compilation; per-load selected-tool manifest readers share overlay readings. Builds delegate source/environment hashing, copying, cold materialized ownership admission and Go execution; per-load digest maps share selected work. Composition can be quadratic in configured plugins/aliases, and native lookup/file/output bytes are not bounded by plugin count alone.
102
- * @evidence contracts/performance.md#reuse-equivalent-work Descriptor hits require the cache's context/environment/runtime/version identity and matching recorded projections, subject to producer declarations and sequential-observation limits. Per-load source/environment maps share directory-keyed digests and one selected transform host serves linked contributors; these identities do not certify every undeclared read or atomic filesystem stability.
105
+ * @evidence contracts/performance.md#reuse-equivalent-work Descriptor hits require the cache's context/environment/runtime/version identity and matching recorded projections, subject to producer declarations and sequential-observation limits. Per-load source/environment maps share directory-keyed digests between package selection and builds, and one selected transform host serves linked contributors. Across processes, the record store opened here proves unchanged sources, toolchain and runtime from separable metadata and answers equal package selections and runtime probes (#1721, #1722, #1723); a store whose root lies inside a plugin source is withdrawn before any source is proven. These identities do not certify every undeclared read or atomic filesystem stability.
103
106
  * @evidence contracts/performance.md#bound-retention-and-release-resources Finally restores injected environment locators, attempts evaluator directory removal and closes direct-evaluator diagnostic descriptors. Close/removal can fail; synchronous completion concerns the selected evaluator and does not certify arbitrary descendants are gone. Generation maps/results scale with observed data, and disk diagnostics/result/observation files have no independent byte ceiling here. Default disk pruning has its own interval/protection/failure policy; explicit roots remain caller-owned.
104
107
  */
105
108
  export function loadProjectPlugins(options: {
@@ -121,6 +124,12 @@ export function loadProjectPlugins(options: {
121
124
  /** Explicit project config, resolved from cwd. */
122
125
  file?: string;
123
126
 
127
+ /**
128
+ * Record store the caller already opened for this invocation, so its later
129
+ * native spawns share it; the loader opens its own when omitted.
130
+ */
131
+ identities?: PluginContentIdentities.Store;
132
+
124
133
  /** Observe plugin build roots before building, including failed builds. */
125
134
  onWatchInputs?: (inputs: readonly string[]) => void;
126
135
 
@@ -168,6 +177,12 @@ export function loadProjectPlugins(options: {
168
177
  */
169
178
  observationsComplete?: false;
170
179
 
180
+ /**
181
+ * Record store this load proved its inputs through, for the invocation's
182
+ * later native spawns (#1723); absent when the project loads no plugin.
183
+ */
184
+ identities?: PluginContentIdentities.Store;
185
+
171
186
  /** Check-stage entries followed by transform-stage entries in stable order. */
172
187
  nativePlugins: ITtscLoadedNativePlugin[];
173
188
 
@@ -234,91 +249,111 @@ export function loadProjectPlugins(options: {
234
249
  };
235
250
  // Where isolated descriptor evaluations keep their answers across launches
236
251
  // (`PluginDescriptorEvaluationCache`).
252
+ // WARNING (#1721, #1722, #1723): a warm load must prove its unchanged inputs
253
+ // from this store, not re-read them. Every launch is a new process, so any
254
+ // module-level memo of this load's work dies with it; a 27 s warm typia
255
+ // launch whose native build took 0.28 s is what that looked like.
256
+ const identities =
257
+ options.identities ??
258
+ PluginContentIdentities.open({
259
+ projectRoot: project.root,
260
+ cacheDir: options.cacheDir,
261
+ env: effectiveEnv,
262
+ });
237
263
  const descriptorCache: DescriptorCacheOptions = {
238
264
  cacheDir: options.cacheDir,
265
+ identities,
239
266
  version: pluginBuildVersions(project.root).ttsc,
240
267
  };
241
- const loadedEntries = withPluginLoaderEnv(() =>
242
- entries.map((entry) => {
243
- const specifier = entry.config.transform;
244
- if (typeof specifier !== "string" || specifier.length === 0) {
245
- throw new Error(
246
- `ttsc: plugin entry is missing a string "transform" field`,
268
+ const loadedEntries = withPluginLoaderEnv(
269
+ () =>
270
+ entries.map((entry) => {
271
+ const specifier = entry.config.transform;
272
+ if (typeof specifier !== "string" || specifier.length === 0) {
273
+ throw new Error(
274
+ `ttsc: plugin entry is missing a string "transform" field`,
275
+ );
276
+ }
277
+ const entryParent = path.join(entry.baseDir, "package.json");
278
+ const probedCandidates = collectModuleResolutionCandidates(
279
+ specifier,
280
+ entryParent,
281
+ undefined,
247
282
  );
248
- }
249
- const entryParent = path.join(entry.baseDir, "package.json");
250
- const probedCandidates = collectModuleResolutionCandidates(
251
- specifier,
252
- entryParent,
253
- undefined,
254
- );
255
- // Capture every candidate before resolution chooses the descriptor entry.
256
- // A post-resolution snapshot could bless a higher-priority file created
257
- // after the resolver had already selected the old entry.
258
- const probedCandidateHashes = hashHostInputPaths(probedCandidates);
259
- const probedCandidateRealpaths = realpathHostInputPaths(probedCandidates);
260
- const request = PluginPackageResolution.resolvePluginRequest(
261
- specifier,
262
- entry.baseDir,
263
- );
264
- // Keep the candidates of the search roots up to the one the entry
265
- // resolved in: the lookup never read the roots after it
266
- // (`moduleResolutionBaseSelects`).
267
- const read = new Set(
268
- collectModuleResolutionCandidates(specifier, entryParent, request).map(
269
- (candidate) => path.resolve(candidate),
270
- ),
271
- );
272
- const entryCandidates = probedCandidates.filter((candidate) =>
273
- read.has(path.resolve(candidate)),
274
- );
275
- const entryCandidateHashes = pickHostInputEntries(
276
- probedCandidateHashes,
277
- entryCandidates,
278
- );
279
- const entryCandidateRealpaths = pickHostInputEntries(
280
- probedCandidateRealpaths,
281
- entryCandidates,
282
- );
283
- const loaded = loadPluginEntry(
284
- entry.config,
285
- { ...context, plugin: entry.config },
286
- request,
287
- effectiveEnv,
288
- descriptorCache,
289
- );
290
- const loadedHostInputHashes = mergeObservedHostInputHashes(
291
- loaded.hostInputHashes,
292
- hashHostInputPaths(Object.keys(loaded.hostInputHashes)),
293
- );
294
- const loadedHostInputRealpaths = mergeObservedHostInputRealpaths(
295
- loaded.hostInputRealpaths,
296
- realpathHostInputPaths(Object.keys(loaded.hostInputRealpaths)),
297
- );
298
- const hostInputHashes = mergeObservedHostInputHashes(
299
- entryCandidateHashes,
300
- loadedHostInputHashes,
301
- );
302
- const hostInputRealpaths = mergeObservedHostInputRealpaths(
303
- entryCandidateRealpaths,
304
- loadedHostInputRealpaths,
305
- );
306
- for (const input of loaded.hostInputs) {
307
- const absolute = path.resolve(input);
308
- if (
309
- !Object.prototype.hasOwnProperty.call(loadedHostInputHashes, absolute)
310
- ) {
311
- delete hostInputHashes[absolute];
283
+ // Capture every candidate before resolution chooses the descriptor entry.
284
+ // A post-resolution snapshot could bless a higher-priority file created
285
+ // after the resolver had already selected the old entry.
286
+ const probedCandidateHashes = hashHostInputPaths(probedCandidates);
287
+ const probedCandidateRealpaths =
288
+ realpathHostInputPaths(probedCandidates);
289
+ const request = PluginPackageResolution.resolvePluginRequest(
290
+ specifier,
291
+ entry.baseDir,
292
+ );
293
+ // Keep the candidates of the search roots up to the one the entry
294
+ // resolved in: the lookup never read the roots after it
295
+ // (`moduleResolutionBaseSelects`).
296
+ const read = new Set(
297
+ collectModuleResolutionCandidates(
298
+ specifier,
299
+ entryParent,
300
+ request,
301
+ ).map((candidate) => path.resolve(candidate)),
302
+ );
303
+ const entryCandidates = probedCandidates.filter((candidate) =>
304
+ read.has(path.resolve(candidate)),
305
+ );
306
+ const entryCandidateHashes = pickHostInputEntries(
307
+ probedCandidateHashes,
308
+ entryCandidates,
309
+ );
310
+ const entryCandidateRealpaths = pickHostInputEntries(
311
+ probedCandidateRealpaths,
312
+ entryCandidates,
313
+ );
314
+ const loaded = loadPluginEntry(
315
+ entry.config,
316
+ { ...context, plugin: entry.config },
317
+ request,
318
+ effectiveEnv,
319
+ descriptorCache,
320
+ );
321
+ const loadedHostInputHashes = mergeObservedHostInputHashes(
322
+ loaded.hostInputHashes,
323
+ hashHostInputPaths(Object.keys(loaded.hostInputHashes)),
324
+ );
325
+ const loadedHostInputRealpaths = mergeObservedHostInputRealpaths(
326
+ loaded.hostInputRealpaths,
327
+ realpathHostInputPaths(Object.keys(loaded.hostInputRealpaths)),
328
+ );
329
+ const hostInputHashes = mergeObservedHostInputHashes(
330
+ entryCandidateHashes,
331
+ loadedHostInputHashes,
332
+ );
333
+ const hostInputRealpaths = mergeObservedHostInputRealpaths(
334
+ entryCandidateRealpaths,
335
+ loadedHostInputRealpaths,
336
+ );
337
+ for (const input of loaded.hostInputs) {
338
+ const absolute = path.resolve(input);
339
+ if (
340
+ !Object.prototype.hasOwnProperty.call(
341
+ loadedHostInputHashes,
342
+ absolute,
343
+ )
344
+ ) {
345
+ delete hostInputHashes[absolute];
346
+ }
312
347
  }
313
- }
314
- return {
315
- ...loaded,
316
- hostInputHashes,
317
- hostInputRealpaths,
318
- hostInputs: [...loaded.hostInputs, ...entryCandidates],
319
- request,
320
- };
321
- }),
348
+ return {
349
+ ...loaded,
350
+ hostInputHashes,
351
+ hostInputRealpaths,
352
+ hostInputs: [...loaded.hostInputs, ...entryCandidates],
353
+ request,
354
+ };
355
+ }),
356
+ identities,
322
357
  );
323
358
  const plugins = composePluginSources(
324
359
  entries,
@@ -342,6 +377,33 @@ export function loadProjectPlugins(options: {
342
377
  ...candidate,
343
378
  ...resolvePluginGoModule(candidate.source, candidate.label),
344
379
  }));
380
+ // One reading of each source directory, shared by package selection and
381
+ // every build below and reported as the state the binaries were keyed on.
382
+ const sourceDigests = new Map<string, string>();
383
+ // And one reading of the environment each build directory is keyed on, which
384
+ // a plugin module root's state reports as it is.
385
+ const environmentDigests = new Map<string, string>();
386
+ // With the witness of each such reading, so a build of a directory package
387
+ // selection already read keys on that reading instead of probing Go again.
388
+ const environmentWitnesses = new Map<
389
+ string,
390
+ PluginBuildEnvironmentWitness.Record
391
+ >();
392
+ const readings = {
393
+ sources: sourceDigests,
394
+ environments: environmentDigests,
395
+ witnesses: environmentWitnesses,
396
+ };
397
+ // Records describing sources must never land inside one, so the store is
398
+ // admitted for source proofs only once every source of the load is known.
399
+ const sourceIdentities = PluginContentIdentities.outside(identities, [
400
+ ...candidates.flatMap((candidate) => [
401
+ candidate.moduleRoot,
402
+ candidate.packageDir,
403
+ ...(candidate.contributors ?? []).map((input) => input.source),
404
+ ]),
405
+ ...SourcePluginWorkspace.findTtscOverlayDirs(),
406
+ ]);
345
407
  // One fixed-effective-environment load owns manifest syntax sharing from the
346
408
  // first watch query through metadata and builds. Do not allocate this after
347
409
  // watch projection: that silently repeats selected-Go work (#1712).
@@ -387,6 +449,8 @@ export function loadProjectPlugins(options: {
387
449
  })),
388
450
  effectiveEnv,
389
451
  packageReaders,
452
+ sourceIdentities,
453
+ readings,
390
454
  );
391
455
  const records = candidates.map(
392
456
  ({ plugin, index, stage, contributors, source, packageDir }) => {
@@ -519,6 +583,8 @@ export function loadProjectPlugins(options: {
519
583
  pluginName: host.label,
520
584
  env: effectiveEnv,
521
585
  readers: packageReaders,
586
+ identities: sourceIdentities,
587
+ readings,
522
588
  packages: [
523
589
  ...hostEntries,
524
590
  ...linkedContributors.map((input) => ({
@@ -554,6 +620,8 @@ export function loadProjectPlugins(options: {
554
620
  standaloneCandidates,
555
621
  effectiveEnv,
556
622
  packageReaders,
623
+ sourceIdentities,
624
+ readings,
557
625
  );
558
626
  standaloneCandidates.forEach((candidate, index) => {
559
627
  NativeSourcePackages.kind(
@@ -574,12 +642,6 @@ export function loadProjectPlugins(options: {
574
642
  }))
575
643
  : []),
576
644
  ];
577
- // One reading of each source directory, shared by every build below and
578
- // reported as the state the binaries were keyed on.
579
- const sourceDigests = new Map<string, string>();
580
- // And one reading of the environment each build directory is keyed on, which
581
- // a plugin module root's state reports as it is.
582
- const environmentDigests = new Map<string, string>();
583
645
  const builtTransformHosts = new Map<object, string>();
584
646
  for (const record of transformHosts) {
585
647
  builtTransformHosts.set(
@@ -590,10 +652,12 @@ export function loadProjectPlugins(options: {
590
652
  contributors: mergeContributors(record.contributors, hostContributors),
591
653
  env: effectiveEnv,
592
654
  goModReaders: packageReaders,
655
+ identities: sourceIdentities,
593
656
  pluginName: record.label,
594
657
  packageOwnership: packageOwnership(record.source, record.label, true),
595
658
  source: record.source,
596
659
  environmentDigests,
660
+ environmentWitnesses,
597
661
  sourceDigests,
598
662
  ttscVersion,
599
663
  tsgoVersion,
@@ -608,6 +672,7 @@ export function loadProjectPlugins(options: {
608
672
  contributors: linkedContributors,
609
673
  env: effectiveEnv,
610
674
  goModReaders: packageReaders,
675
+ identities: sourceIdentities,
611
676
  label: "linked plugin host",
612
677
  pluginName: "linked-plugin-host",
613
678
  source: path.join(ttscPackageRoot(), "cmd", "utility-host"),
@@ -617,6 +682,7 @@ export function loadProjectPlugins(options: {
617
682
  true,
618
683
  ),
619
684
  environmentDigests,
685
+ environmentWitnesses,
620
686
  sourceDigests,
621
687
  ttscVersion,
622
688
  tsgoVersion,
@@ -638,6 +704,7 @@ export function loadProjectPlugins(options: {
638
704
  contributors: record.contributors,
639
705
  env: effectiveEnv,
640
706
  goModReaders: packageReaders,
707
+ identities: sourceIdentities,
641
708
  pluginName: record.label,
642
709
  packageOwnership: packageOwnership(
643
710
  record.source,
@@ -646,6 +713,7 @@ export function loadProjectPlugins(options: {
646
713
  ),
647
714
  source: record.source,
648
715
  environmentDigests,
716
+ environmentWitnesses,
649
717
  sourceDigests,
650
718
  ttscVersion,
651
719
  tsgoVersion,
@@ -697,6 +765,7 @@ export function loadProjectPlugins(options: {
697
765
  entry.observationsComplete && declaresHostInputReads(entry.plugin),
698
766
  ),
699
767
  discoveryInputsComplete: project.configInputsComplete === true,
768
+ ...(identities === undefined ? {} : { identities }),
700
769
  nativePlugins: orderNativePlugins(nativePlugins),
701
770
  ...(loadedEntries.some((entry) => !entry.observationsComplete)
702
771
  ? { observationsComplete: false as const }
@@ -712,9 +781,14 @@ export function loadProjectPlugins(options: {
712
781
  pluginSourceState(directory, {
713
782
  env: effectiveEnv,
714
783
  sourceDigest,
715
- ...(environmentDigests.has(directory)
716
- ? { environment: environmentDigests.get(directory)! }
717
- : {}),
784
+ environment:
785
+ environmentDigests.get(directory) ??
786
+ pluginBuildEnvironment(
787
+ directory,
788
+ effectiveEnv,
789
+ undefined,
790
+ sourceIdentities,
791
+ ),
718
792
  }),
719
793
  ]),
720
794
  ),
@@ -1290,7 +1364,12 @@ function loadPluginDescriptor(
1290
1364
  ) {
1291
1365
  throw error;
1292
1366
  }
1293
- const loaded = loadDescriptorViaTtsx(request, context, effectiveEnv);
1367
+ const loaded = loadDescriptorViaTtsx(
1368
+ request,
1369
+ context,
1370
+ effectiveEnv,
1371
+ descriptorCache.identities,
1372
+ );
1294
1373
  if (loaded === undefined) {
1295
1374
  throw error;
1296
1375
  }
@@ -1303,6 +1382,9 @@ interface DescriptorCacheOptions {
1303
1382
  /** The plugin cache directory the caller selected, if any. */
1304
1383
  cacheDir: string | undefined;
1305
1384
 
1385
+ /** Record store that proves the evaluating runtimes from metadata (#1723). */
1386
+ identities: PluginContentIdentities.Store | undefined;
1387
+
1306
1388
  /** This ttsc build's version. */
1307
1389
  version: string;
1308
1390
  }
@@ -1352,21 +1434,28 @@ function loadCommonJsDescriptor(
1352
1434
  runtime,
1353
1435
  effectiveEnv,
1354
1436
  context.projectRoot,
1437
+ descriptorCache.identities,
1355
1438
  );
1356
1439
  const node =
1357
1440
  !runtimeCapabilities.bun &&
1358
1441
  runtimeCapabilities.registerHooks &&
1359
1442
  runtimeCapabilities.executable !== undefined
1360
1443
  ? runtimeCapabilities.executable
1361
- : resolveNodeBinary(effectiveEnv, context.projectRoot);
1444
+ : resolveNodeBinary(
1445
+ effectiveEnv,
1446
+ context.projectRoot,
1447
+ descriptorCache.identities,
1448
+ );
1362
1449
  const cacheAuthority = {
1363
1450
  additionalRuntime: node,
1364
1451
  cacheDir: descriptorCache.cacheDir,
1452
+ identities: descriptorCache.identities,
1365
1453
  context,
1366
1454
  // Everything the child receives besides the per-evaluation output paths.
1367
1455
  env: {
1368
1456
  ...effectiveEnv,
1369
1457
  ...(node === undefined ? {} : { TTSC_NODE_BINARY: node }),
1458
+ ...descriptorRecordRoot(descriptorCache.identities),
1370
1459
  TTSC_TTSX_BINARY: ttsx,
1371
1460
  },
1372
1461
  projectRoot: context.projectRoot,
@@ -1456,6 +1545,7 @@ function loadCommonJsDescriptor(
1456
1545
  // The direct evaluator may be Bun, but ttsx and native config
1457
1546
  // loaders require a real Node runtime with synchronous hooks.
1458
1547
  ...(node === undefined ? {} : { TTSC_NODE_BINARY: node }),
1548
+ ...descriptorRecordRoot(descriptorCache.identities),
1459
1549
  TTSC_TTSX_BINARY: ttsx,
1460
1550
  TTSC_PLUGIN_CONTEXT: JSON.stringify(context),
1461
1551
  TTSC_PLUGIN_DESCRIPTOR_LOAD: "1",
@@ -1840,11 +1930,12 @@ function loadDescriptorViaTtsx(
1840
1930
  request: string,
1841
1931
  context: ITtscPluginFactoryContext,
1842
1932
  effectiveEnv: NodeJS.ProcessEnv,
1933
+ identities: PluginContentIdentities.Store | undefined,
1843
1934
  ): IsolatedPluginDescriptor | undefined {
1844
1935
  // Binary discovery prefers the instance environment, then the ambient
1845
1936
  // process.env (where `withPluginLoaderEnv` injects ttsc's own node/ttsx paths
1846
1937
  // just before this runs), then the running interpreter.
1847
- const node = resolveNodeBinary(effectiveEnv, context.projectRoot);
1938
+ const node = resolveNodeBinary(effectiveEnv, context.projectRoot, identities);
1848
1939
  const ttsx = effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY;
1849
1940
  if (node === undefined || ttsx === undefined || ttsx.length === 0) {
1850
1941
  return undefined;
@@ -1883,6 +1974,7 @@ function loadDescriptorViaTtsx(
1883
1974
  // may recurse into further descriptor loads) finds them even when the
1884
1975
  // instance-env snapshot predates `withPluginLoaderEnv`.
1885
1976
  TTSC_NODE_BINARY: node,
1977
+ ...descriptorRecordRoot(identities),
1886
1978
  TTSC_TTSX_BINARY: ttsx,
1887
1979
  TTSC_PLUGIN_CONTEXT: JSON.stringify({
1888
1980
  binary: context.binary,
@@ -1967,6 +2059,20 @@ interface TtsxDescriptorResolutionRecord {
1967
2059
  unstable?: boolean;
1968
2060
  }
1969
2061
 
2062
+ /**
2063
+ * The private variable that hands a descriptor evaluator the plugin cache root
2064
+ * this load's record store selected, so the child proves the compiler it runs
2065
+ * from that root's records instead of streaming it (#1726) and never selects a
2066
+ * cache root of its own. Absent without a store.
2067
+ */
2068
+ function descriptorRecordRoot(
2069
+ identities: PluginContentIdentities.Store | undefined,
2070
+ ): { TTSC_PLUGIN_RECORD_ROOT?: string } {
2071
+ return identities === undefined
2072
+ ? {}
2073
+ : { TTSC_PLUGIN_RECORD_ROOT: identities.root };
2074
+ }
2075
+
1970
2076
  /**
1971
2077
  * Expand the ttsx runtime's selected module edges into the same exact and
1972
2078
  * missing resolution inputs used by the direct isolated evaluator.
@@ -2038,6 +2144,15 @@ function readTtsxDescriptorInputs(
2038
2144
  (signatures.has(resolved) &&
2039
2145
  signatures.get(resolved) !== record.signature)
2040
2146
  ) {
2147
+ E2ETrace.capabilityResolution(
2148
+ "plugin-descriptor-input-signature-moved",
2149
+ {
2150
+ resolved,
2151
+ first: signatures.get(resolved),
2152
+ later:
2153
+ typeof record.signature === "string" ? record.signature : null,
2154
+ },
2155
+ );
2041
2156
  hashes.delete(resolved);
2042
2157
  realpaths.delete(resolved);
2043
2158
  signatures.delete(resolved);
@@ -2115,10 +2230,13 @@ function errorMessage(error: unknown): string {
2115
2230
  return error instanceof Error ? error.message : String(error);
2116
2231
  }
2117
2232
 
2118
- function withPluginLoaderEnv<T>(run: () => T): T {
2233
+ function withPluginLoaderEnv<T>(
2234
+ run: () => T,
2235
+ identities: PluginContentIdentities.Store | undefined,
2236
+ ): T {
2119
2237
  const previousNode = process.env.TTSC_NODE_BINARY;
2120
2238
  const previousTtsx = process.env.TTSC_TTSX_BINARY;
2121
- const node = resolveNodeBinary({}, process.cwd());
2239
+ const node = resolveNodeBinary({}, process.cwd(), identities);
2122
2240
  if (process.env.TTSC_NODE_BINARY === undefined && node !== undefined) {
2123
2241
  process.env.TTSC_NODE_BINARY = node;
2124
2242
  }
@@ -1,10 +1,12 @@
1
1
  import fs from "node:fs";
2
+ import path from "node:path";
2
3
 
3
4
  import { CapabilityResolutionFormat } from "./CapabilityResolutionFormat";
4
5
  import type { ITtscCapabilityPluginSource } from "./ITtscCapabilityPluginSource";
5
6
  import type { ITtscCapabilityResolutionEntry } from "./ITtscCapabilityResolutionEntry";
6
7
  import { hashHostInputPaths } from "./load/hashHostInputPaths";
7
8
  import { realpathHostInputPaths } from "./load/realpathHostInputPaths";
9
+ import { PluginContentIdentities } from "./source/PluginContentIdentities";
8
10
  import { pluginSourceFilesSignature } from "./source/pluginSourceFilesSignature";
9
11
  import { pluginSourceStateHolds } from "./source/pluginSourceStateHolds";
10
12
  import { recordCacheFileUse } from "./source/recordCacheFileUse";
@@ -52,6 +54,12 @@ export function readCapabilityResolution(options: {
52
54
 
53
55
  /** Environment selecting the cache root; ambient process env when omitted. */
54
56
  env?: NodeJS.ProcessEnv;
57
+
58
+ /**
59
+ * Record store of the project's plugin cache, which proves the runtime and
60
+ * the plugin sources and SDK from metadata instead of their bytes (#1725).
61
+ */
62
+ identities?: PluginContentIdentities.Store;
55
63
  }): ITtscCapabilityResolutionEntry | null {
56
64
  const file = CapabilityResolutionFormat.resolutionFile(options);
57
65
  if (file === null) return null;
@@ -83,12 +91,14 @@ export function readCapabilityResolution(options: {
83
91
  // old one still exists.
84
92
  const sources = Object.entries(entry.pluginSources);
85
93
  const evidence = sources.some(([, source]) => source.signature !== undefined)
86
- ? CapabilityResolutionFormat.sourceEvidence(
87
- CapabilityResolutionFormat.clockReference(file),
94
+ ? PluginContentIdentities.sourceEvidence(
95
+ PluginContentIdentities.mintReference(path.dirname(file)),
88
96
  )
89
97
  : undefined;
98
+ const projectRoot = path.resolve(options.cwd);
90
99
  for (const [directory, source] of sources)
91
- if (!pluginSourceProven(directory, source, evidence)) return null;
100
+ if (!pluginSourceProven(directory, source, evidence, projectRoot))
101
+ return null;
92
102
  for (const plugin of entry.plugins)
93
103
  if (!fs.existsSync(plugin.binary)) return null;
94
104
  recordCacheFileUse(file);
@@ -108,8 +118,9 @@ function pluginSourceProven(
108
118
  directory: string,
109
119
  source: ITtscCapabilityPluginSource,
110
120
  evidence:
111
- | ReturnType<typeof CapabilityResolutionFormat.sourceEvidence>
121
+ | ReturnType<typeof PluginContentIdentities.sourceEvidence>
112
122
  | undefined,
123
+ projectRoot: string,
113
124
  ): boolean {
114
125
  try {
115
126
  const now =
@@ -123,8 +134,8 @@ function pluginSourceProven(
123
134
  now.separable &&
124
135
  now.signature === source.signature &&
125
136
  source.digest !== undefined
126
- ? { sourceDigest: source.digest }
127
- : {},
137
+ ? { projectRoot, sourceDigest: source.digest }
138
+ : { projectRoot },
128
139
  );
129
140
  } catch {
130
141
  return false;