ttsc 0.31.1 → 0.31.3

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 (146) hide show
  1. package/lib/compiler/internal/build/BuildExecution.d.ts +6 -3
  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/build/RunBuildOptions.d.ts +2 -1
  5. package/lib/compiler/internal/startResidentTransform.js +5 -4
  6. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  7. package/lib/compiler/internal/transformProjectInMemory.js +9 -7
  8. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  9. package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
  10. package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
  11. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  12. package/lib/internal/pluginSource.d.ts +1 -0
  13. package/lib/internal/pluginSource.js +1 -0
  14. package/lib/internal/pluginSource.js.map +1 -1
  15. package/lib/internal/resolveNodeBinary.d.ts +6 -1
  16. package/lib/internal/resolveNodeBinary.js +6 -2
  17. package/lib/internal/resolveNodeBinary.js.map +1 -1
  18. package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
  19. package/lib/internal/runtimeExecutableIdentity.js +192 -102
  20. package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
  21. package/lib/launcher/internal/runTtsc.js +2 -2
  22. package/lib/launcher/internal/runTtsc.js.map +1 -1
  23. package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
  24. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  25. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
  26. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
  27. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
  28. package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
  29. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  30. package/lib/launcher/internal/watch/WatchTopology.d.ts +20 -3
  31. package/lib/launcher/internal/watch/WatchTopology.js +31 -9
  32. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  33. package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
  34. package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
  35. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  36. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
  37. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
  38. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  39. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +17 -3
  40. package/lib/plugin/internal/load/loadProjectPlugins.js +86 -23
  41. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  42. package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
  43. package/lib/plugin/internal/readCapabilityResolution.js +8 -5
  44. package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
  45. package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +9 -3
  46. package/lib/plugin/internal/source/GoEnvironmentReading.js +53 -7
  47. package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -1
  48. package/lib/plugin/internal/source/NativeSourcePackages.d.ts +44 -6
  49. package/lib/plugin/internal/source/NativeSourcePackages.js +150 -8
  50. package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
  51. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -3
  52. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +32 -7
  53. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  54. package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
  55. package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
  56. package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
  57. package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
  58. package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
  59. package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
  60. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
  61. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
  62. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  63. package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +13 -2
  64. package/lib/plugin/internal/source/SourcePluginWorkspace.js +140 -4
  65. package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
  66. package/lib/plugin/internal/source/buildSourcePlugin.d.ts +15 -2
  67. package/lib/plugin/internal/source/buildSourcePlugin.js +27 -4
  68. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  69. package/lib/plugin/internal/source/computeCacheKey.d.ts +21 -5
  70. package/lib/plugin/internal/source/computeCacheKey.js +71 -29
  71. package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
  72. package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
  73. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +74 -35
  74. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
  75. package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
  76. package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
  77. package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
  78. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
  79. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
  80. package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
  81. package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
  82. package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
  83. package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
  84. package/lib/plugin/internal/source/pluginSourceState.js +20 -8
  85. package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
  86. package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
  87. package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
  88. package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
  89. package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
  90. package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
  91. package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
  92. package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
  93. package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
  94. package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
  95. package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
  96. package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
  97. package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
  98. package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
  99. package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
  100. package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
  101. package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
  102. package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
  103. package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
  104. package/lib/plugin/resolveCapabilityPlugins.js +67 -15
  105. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
  106. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -1
  107. package/package.json +8 -8
  108. package/src/compiler/internal/build/BuildExecution.ts +48 -14
  109. package/src/compiler/internal/build/RunBuildOptions.ts +2 -1
  110. package/src/compiler/internal/startResidentTransform.ts +12 -3
  111. package/src/compiler/internal/transformProjectInMemory.ts +10 -2
  112. package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
  113. package/src/internal/pluginSource.ts +1 -0
  114. package/src/internal/resolveNodeBinary.ts +12 -1
  115. package/src/internal/runtimeExecutableIdentity.ts +227 -141
  116. package/src/launcher/internal/runTtsc.ts +6 -2
  117. package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
  118. package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
  119. package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
  120. package/src/launcher/internal/watch/WatchTopology.ts +38 -9
  121. package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
  122. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
  123. package/src/plugin/internal/load/loadProjectPlugins.ts +226 -98
  124. package/src/plugin/internal/readCapabilityResolution.ts +17 -6
  125. package/src/plugin/internal/source/GoEnvironmentReading.ts +74 -5
  126. package/src/plugin/internal/source/NativeSourcePackages.ts +262 -5
  127. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +41 -5
  128. package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
  129. package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
  130. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
  131. package/src/plugin/internal/source/SourcePluginWorkspace.ts +176 -2
  132. package/src/plugin/internal/source/buildSourcePlugin.ts +43 -3
  133. package/src/plugin/internal/source/computeCacheKey.ts +103 -47
  134. package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +144 -29
  135. package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
  136. package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
  137. package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
  138. package/src/plugin/internal/source/pluginSourceState.ts +28 -12
  139. package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
  140. package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
  141. package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
  142. package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
  143. package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
  144. package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
  145. package/src/plugin/resolveCapabilityPlugins.ts +87 -13
  146. package/src/structures/internal/TtscSingleFileEmitOptions.ts +8 -1
@@ -15,11 +15,13 @@ import { runBuild } from "../../../compiler/internal/build/runBuild";
15
15
  import { readProjectConfig } from "../../../compiler/internal/project/readProjectConfig";
16
16
  import { resolveOwningProjectConfig } from "../../../compiler/internal/project/resolveOwningProjectConfig";
17
17
  import { resolveTsgo } from "../../../compiler/internal/resolveTsgo";
18
+ import { SidecarEnvironment } from "../../../compiler/internal/sharedHost/SidecarEnvironment";
18
19
  import { spawnNative } from "../../../compiler/internal/spawnNative";
19
20
  import { E2ETrace } from "../../../internal/E2ETrace";
20
21
  import { createCanonicalTempDirectory } from "../../../internal/createCanonicalTempDirectory";
21
22
  import { runtimeExecutableIdentity } from "../../../internal/runtimeExecutableIdentity";
22
23
  import { moduleResolutionBaseSelects } from "../../../plugin/internal/load/moduleResolutionBaseSelects";
24
+ import { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
23
25
  import { recordCacheFileUse } from "../../../plugin/internal/source/recordCacheFileUse";
24
26
  import { buildSingleRootProject } from "../buildSingleRootProject";
25
27
  import { inlineServedSourceMap } from "../inlineServedSourceMap";
@@ -684,6 +686,17 @@ function observePluginDescriptorInputOnce(record: {
684
686
  (record.hash !== undefined && record.hash !== observedHash) ||
685
687
  (record.realpath !== undefined && record.realpath !== observedRealpath) ||
686
688
  (record.signature !== undefined && record.signature !== afterSignature);
689
+ // A missing path's signature is its nearest existing ancestor's metadata,
690
+ // so a write anywhere in that ancestor refuses the proof; the trace names
691
+ // which observation moved.
692
+ if (unstable)
693
+ E2ETrace.capabilityResolution("plugin-descriptor-input-unstable", {
694
+ resolved: record.resolved,
695
+ recordedSignature: record.signature,
696
+ beforeSignature,
697
+ afterSignature,
698
+ hashChanged: record.hash !== undefined && record.hash !== observedHash,
699
+ });
687
700
  return `${JSON.stringify({
688
701
  ...record,
689
702
  hash: observedHash,
@@ -1479,12 +1492,63 @@ function orphanCacheRoot(): string {
1479
1492
  * Read afresh at every use, never remembered by path: a long-lived process can
1480
1493
  * lower orphans before and after the compiler at that path is replaced, and an
1481
1494
  * entry lowered by the new one must not be recorded under the old one's key for
1482
- * a later process to adopt.
1495
+ * a later process to adopt. A fresh use proves an unchanged compiler from the
1496
+ * run's plugin cache records (`runtimeRecordStore`), which observe its metadata
1497
+ * again each time.
1483
1498
  */
1484
1499
  function compilerIdentity(binary: string): string {
1485
- return runtimeExecutableIdentity(binary) ?? crypto.randomUUID();
1500
+ return (
1501
+ runtimeExecutableIdentity(binary, runtimeRecordStore()) ??
1502
+ crypto.randomUUID()
1503
+ );
1486
1504
  }
1487
1505
 
1506
+ /**
1507
+ * The record store of this run's plugin cache root, or `undefined` without one.
1508
+ *
1509
+ * WARNING (#1726): every ttsx process and every descriptor evaluation streamed
1510
+ * the whole compiler executable (24.5 MB) to key its orphan and dependency
1511
+ * builds, although the bytes had not changed since the last process. The root
1512
+ * is the one a descriptor evaluator's parent load selected
1513
+ * (`TTSC_PLUGIN_RECORD_ROOT`), or the cache root of this run's manifest; a
1514
+ * process with neither streams as before and never selects a root itself.
1515
+ */
1516
+ function runtimeRecordStore(): PluginContentIdentities.Store | undefined {
1517
+ const descriptorRoot =
1518
+ process.env.TTSC_PLUGIN_DESCRIPTOR_LOAD === "1"
1519
+ ? SidecarEnvironment.read(process.env, "TTSC_PLUGIN_RECORD_ROOT")
1520
+ : undefined;
1521
+ const owner = RuntimeManifestRegistry.runtimeManifests().find(
1522
+ (candidate) =>
1523
+ typeof candidate.orphanCacheDir === "string" &&
1524
+ candidate.orphanCacheDir.length !== 0,
1525
+ );
1526
+ const root =
1527
+ descriptorRoot !== undefined && path.isAbsolute(descriptorRoot)
1528
+ ? descriptorRoot
1529
+ : owner === undefined
1530
+ ? undefined
1531
+ : path.dirname(owner.orphanCacheDir!);
1532
+ if (root === undefined) return undefined;
1533
+ // One store per root serves the process: its references only grow older,
1534
+ // which can make fewer stamps separable but never more, and each identity
1535
+ // still observes the compiler's metadata afresh.
1536
+ if (runtimeStore?.root !== root)
1537
+ runtimeStore = {
1538
+ root,
1539
+ store: PluginContentIdentities.open({
1540
+ projectRoot: root,
1541
+ cacheDir: root,
1542
+ env: process.env,
1543
+ }),
1544
+ };
1545
+ return runtimeStore.store;
1546
+ }
1547
+
1548
+ let runtimeStore:
1549
+ | { root: string; store: PluginContentIdentities.Store | undefined }
1550
+ | undefined;
1551
+
1488
1552
  /** The version of this ttsc package, which owns the orphan post-processing. */
1489
1553
  function ownPackageVersion(): string {
1490
1554
  if (ownPackageVersionCache === undefined) {
@@ -2020,6 +2084,7 @@ function dependencyCachePaths(
2020
2084
  try {
2021
2085
  compilerProof = runtimeExecutableIdentity(
2022
2086
  resolveTsgo({ cwd: path.dirname(tsconfig) }).binary,
2087
+ runtimeRecordStore(),
2023
2088
  );
2024
2089
  } catch {
2025
2090
  // An unobservable executable gets a unique, non-reusable generation key.
@@ -2045,7 +2110,7 @@ function dependencyCachePaths(
2045
2110
  function assertCompilerStillCurrent(tsconfig: string, proof?: string): void {
2046
2111
  if (proof === undefined) return;
2047
2112
  const binary = resolveTsgo({ cwd: path.dirname(tsconfig) }).binary;
2048
- const current = runtimeExecutableIdentity(binary);
2113
+ const current = runtimeExecutableIdentity(binary, runtimeRecordStore());
2049
2114
  if (current !== proof) {
2050
2115
  E2ETrace.capabilityResolution("runtime-compiler-publication-refused", {
2051
2116
  tsconfig,
@@ -34,7 +34,8 @@ import { LSPProjectInputDigest } from "./LSPProjectInputDigest";
34
34
  * detected drift; unavailable markers or changes between reads are not a proof
35
35
  * that the complete filesystem stayed fixed throughout capture.
36
36
  *
37
- * @param loaded The plugin load of the session.
37
+ * @param loaded The plugin load of the session, with the project whose plugin
38
+ * cache proves its plugin sources from records.
38
39
  * @returns The inputs, or `undefined` when the load no longer describes the
39
40
  * filesystem and the selection has to be loaded again.
40
41
  * @evidence contracts/common.md#principled-implementation The manifest groups candidate/source-file digests by directory. Recorded content and physical-target proofs and all plugin-source states are compared again before return; a detected mismatch rejects capture, while sequential reads and unproven paths do not certify every post-load change.
@@ -53,6 +54,7 @@ export function captureLSPPluginSelectionInputs(loaded: {
53
54
  hostInputRealpaths: Readonly<Record<string, string | null>>;
54
55
  hostInputs: readonly string[];
55
56
  pluginSources: Readonly<Record<string, string>>;
57
+ project?: { root: string };
56
58
  }): ILSPPluginSelectionInputs | undefined {
57
59
  const excluded = new Set(
58
60
  loaded.deferredHostInputs.map((file) => path.resolve(file)),
@@ -85,8 +87,15 @@ export function captureLSPPluginSelectionInputs(loaded: {
85
87
  )
86
88
  )
87
89
  return undefined;
90
+ // The load's project names the plugin cache whose records prove the sources
91
+ // and the SDK, so a session start does not read them again (#1725).
88
92
  for (const [directory, state] of Object.entries(loaded.pluginSources))
89
- if (!pluginSourceStateHolds(directory, state)) return undefined;
93
+ if (
94
+ !pluginSourceStateHolds(directory, state, {
95
+ projectRoot: loaded.project?.root,
96
+ })
97
+ )
98
+ return undefined;
90
99
  return {
91
100
  omittedNames: GoSourceInputs.OMITTED_SOURCE_FILE_NAMES,
92
101
  omittedSuffixes: GoSourceInputs.OMITTED_SOURCE_FILE_SUFFIXES,
@@ -13,6 +13,7 @@ import { spawnNative } from "../../../compiler/internal/spawnNative";
13
13
  import { E2ETrace } from "../../../internal/E2ETrace";
14
14
  import { resolveNodeBinary } from "../../../internal/resolveNodeBinary";
15
15
  import { loadProjectPlugins } from "../../../plugin/internal/load/loadProjectPlugins";
16
+ import type { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
16
17
  import type { ITtscLoadedNativePlugin } from "../../../structures/internal/ITtscLoadedNativePlugin";
17
18
  import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
18
19
  import type { ITtscProjectIdentity } from "../../../structures/internal/ITtscProjectIdentity";
@@ -165,6 +166,13 @@ type LSPExecutionContext = {
165
166
  */
166
167
  selectionInputs?: ILSPPluginSelectionInputs;
167
168
 
169
+ /**
170
+ * Record store the confirming plugin load opened, which proves the Node
171
+ * runtime from metadata instead of streaming it again (#1726); absent for a
172
+ * project without plugins, which gains no cache directory.
173
+ */
174
+ identities?: PluginContentIdentities.Store;
175
+
168
176
  tsgoBinary: string;
169
177
  };
170
178
 
@@ -196,6 +204,7 @@ function resolveTtscserverEnv(launch: LSPLaunchOptions): TtscserverEnvironment {
196
204
  const context = resolveLspExecutionContext(launch);
197
205
  const env = lspSidecarEnvironment({
198
206
  cwd: context.projectContext?.logicalProjectRoot ?? path.resolve(launch.cwd),
207
+ identities: context.identities,
199
208
  pluginConfigOrigin: context.projectContext?.pluginConfigOrigin,
200
209
  tsgoBinary: context.tsgoBinary,
201
210
  });
@@ -244,6 +253,7 @@ function resolveTtscserverEnv(launch: LSPLaunchOptions): TtscserverEnvironment {
244
253
 
245
254
  function lspSidecarEnvironment(options: {
246
255
  cwd: string;
256
+ identities: PluginContentIdentities.Store | undefined;
247
257
  pluginConfigOrigin: string | undefined;
248
258
  tsgoBinary: string;
249
259
  }): NodeJS.ProcessEnv {
@@ -253,7 +263,7 @@ function lspSidecarEnvironment(options: {
253
263
  SidecarEnvironment.read(process.env, "TTSC_TTSX_BINARY") ??
254
264
  path.join(__dirname, "..", "..", "..", "launcher", "ttsx.js"),
255
265
  });
256
- const node = resolveNodeBinary(env, options.cwd);
266
+ const node = resolveNodeBinary(env, options.cwd, options.identities);
257
267
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
258
268
  SidecarEnvironment.write(
259
269
  env,
@@ -305,6 +315,7 @@ function resolveLspExecutionContext(
305
315
  resolveFrom: __filename,
306
316
  });
307
317
  const initialProjectInputs = captureInitialLSPProjectInputs({
318
+ identities: loaded.identities,
308
319
  nativePlugins: loaded.nativePlugins,
309
320
  pluginConfigOrigin,
310
321
  project: selectedProject,
@@ -323,6 +334,7 @@ function resolveLspExecutionContext(
323
334
  resolveFrom: __filename,
324
335
  });
325
336
  const confirmedProjectInputs = captureInitialLSPProjectInputs({
337
+ identities: confirmation.identities,
326
338
  nativePlugins: confirmation.nativePlugins,
327
339
  pluginConfigOrigin,
328
340
  project: confirmedProject,
@@ -345,6 +357,9 @@ function resolveLspExecutionContext(
345
357
  initialProjectInputs: confirmedProjectInputs,
346
358
  nativePlugins: confirmation.nativePlugins,
347
359
  selectionInputs,
360
+ ...(confirmation.identities === undefined
361
+ ? {}
362
+ : { identities: confirmation.identities }),
348
363
  projectContext: {
349
364
  ...confirmedProject.identity,
350
365
  ...(pluginConfigOrigin === undefined ? {} : { pluginConfigOrigin }),
@@ -377,6 +392,7 @@ function loadLSPProjectPlugins(
377
392
  }
378
393
 
379
394
  function captureInitialLSPProjectInputs(options: {
395
+ identities: PluginContentIdentities.Store | undefined;
380
396
  nativePlugins: readonly ITtscLoadedNativePlugin[];
381
397
  pluginConfigOrigin: string | undefined;
382
398
  project: ITtscParsedProjectConfig;
@@ -414,6 +430,7 @@ function captureInitialLSPProjectInputs(options: {
414
430
  }
415
431
  const env = lspSidecarEnvironment({
416
432
  cwd: options.project.root,
433
+ identities: options.identities,
417
434
  pluginConfigOrigin: options.pluginConfigOrigin,
418
435
  tsgoBinary: options.tsgoBinary,
419
436
  });
@@ -14,9 +14,9 @@ import { type ProjectInputPathIdentityContext } from "../../../internal/pathIden
14
14
  import { createProjectInputPathIdentityContext } from "../../../internal/pathIdentity/createProjectInputPathIdentityContext";
15
15
  import { isProjectInputPathIdentityWithin } from "../../../internal/pathIdentity/isProjectInputPathIdentityWithin";
16
16
  import { resolveProjectInputPath } from "../../../internal/pathIdentity/resolveProjectInputPath";
17
+ import { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
17
18
  import { collectPluginSourceDirectories } from "../../../plugin/internal/source/collectPluginSourceDirectories";
18
19
  import { pluginSourceCovers } from "../../../plugin/internal/source/pluginSourceCovers";
19
- import { pluginSourceDigest } from "../../../plugin/internal/source/pluginSourceDigest";
20
20
  import { prunesPluginSourceDirectory } from "../../../plugin/internal/source/prunesPluginSourceDirectory";
21
21
  import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
22
22
  import type { ITtscProjectInputSnapshot } from "../../../structures/internal/ITtscProjectInputSnapshot";
@@ -84,6 +84,13 @@ export class WatchTopology {
84
84
  */
85
85
  private pluginInputStates = new Map<string, string>();
86
86
 
87
+ /**
88
+ * The record store of the latest plugin load that reported the inputs, which
89
+ * proves their states from the plugin cache's records (#1728). Absent before
90
+ * a load reports one, or when it opened none; the states are read then.
91
+ */
92
+ private pluginInputIdentities: PluginContentIdentities.Store | undefined;
93
+
87
94
  /**
88
95
  * The plugin inputs notifications named since the last decision, by path key,
89
96
  * each with the locations noted for it (`notePluginNotification`).
@@ -331,17 +338,33 @@ export class WatchTopology {
331
338
  * observation. This does not freeze the source tree or prove every transient
332
339
  * edit was seen.
333
340
  *
341
+ * WARNING (#1728): take each baseline through the reporting load's record
342
+ * store (`identities`), never by reading the sources again. The load proves
343
+ * the same directories from the plugin cache's records a moment later, and a
344
+ * content read here cost every watch session start 0.3-0.6 s for typia's Go
345
+ * module. The store proves only a digest whose metadata still holds and is
346
+ * separable from its reference, so the baseline is the digest a content read
347
+ * would return at this moment.
348
+ *
349
+ * @param inputs Plugin source directories and files the load reported.
350
+ * @param identities The reporting load's record store, if it opened one.
334
351
  * @evidence contracts/common.md#principled-implementation Published plugin inputs define the source corpus, and retaining existing pre-build baselines keeps unreported edits visible.
335
352
  * @evidence contracts/common.md#clear-and-simple-design Deduplication, baseline retention and compiler/watch reconciliation remain ordered phases.
336
353
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The actual plugin source digest applies its owning source-selection and pruning policy; named plugins or fixture output do not replace observed input state.
337
354
  * @evidence contracts/common.md#meaningful-documentation Native prose explains baseline timing and why existing inputs are not restamped following the documentation skill.
338
355
  * @evidence contracts/portability.md#os-neutral-implementation Native path resolution and canonical backend registration govern source trees; lexical keys preserve declarations and unknown names, folding ASCII only under measured insensitive parents rather than an OS-derived rule.
339
- * @evidence contracts/performance.md#efficient-algorithms Input/path keys deduplicate with text and native case/ancestor work; only newly admitted inputs compute initial source digests. Those reads scan the selected pruned corpus, then refresh(false) also pays project/compiler listing, directory traversal and watcher reconciliation; fixed publication count does not bound delegated bytes or backend work.
340
- * @evidence contracts/performance.md#reuse-equivalent-work Unchanged ordered input sets return immediately; existing inputs reuse their recorded digest until actual notification processing establishes changed consumed state.
356
+ * @evidence contracts/performance.md#efficient-algorithms Input/path keys deduplicate with text and native case/ancestor work; only newly admitted inputs compute initial source digests, each proven from the selected files' metadata when the load's records hold and read from the selected pruned corpus otherwise; refresh(false) also pays project/compiler listing, directory traversal and watcher reconciliation; fixed publication count does not bound delegated bytes or backend work.
357
+ * @evidence contracts/performance.md#reuse-equivalent-work Unchanged ordered input sets return immediately; existing inputs reuse their recorded digest until actual notification processing establishes changed consumed state; a new input's digest is the one the load recorded while its metadata holds.
341
358
  * @evidence contracts/performance.md#bound-retention-and-release-resources Publications replace input/digest populations and case-observation caches without a population cap; synchronization attempts obsolete watcher closure. Native/supplied closure has its own lifetime and can throw; owner close does not prove every backend has stopped or every retained snapshot byte was released.
342
359
  */
343
- public setExtraInputs(inputs: readonly string[]): void {
360
+ public setExtraInputs(
361
+ inputs: readonly string[],
362
+ identities?: PluginContentIdentities.Store,
363
+ ): void {
344
364
  this.beginLexicalTransaction();
365
+ // The latest load's store also serves later notifications; its references
366
+ // only grow older, which can make fewer stamps separable but never more.
367
+ this.pluginInputIdentities = identities;
345
368
  const next = uniqueExistingPaths(inputs, this.lexicalIdentities);
346
369
  if (arraysEqual(this.extraInputs, next)) return;
347
370
  this.extraInputs = next;
@@ -354,7 +377,8 @@ export class WatchTopology {
354
377
  const key = this.pathKey(input);
355
378
  states.set(
356
379
  key,
357
- this.pluginInputStates.get(key) ?? pluginInputState(input),
380
+ this.pluginInputStates.get(key) ??
381
+ pluginInputState(input, this.pluginInputIdentities),
358
382
  );
359
383
  }
360
384
  this.pluginInputStates = states;
@@ -1664,7 +1688,7 @@ export class WatchTopology {
1664
1688
  const key = this.pathKey(input);
1665
1689
  const locations = pending.get(key);
1666
1690
  if (locations === undefined) continue;
1667
- const state = pluginInputState(input);
1691
+ const state = pluginInputState(input, this.pluginInputIdentities);
1668
1692
  if (this.pluginInputStates.get(key) === state) continue;
1669
1693
  this.pluginInputStates.set(key, state);
1670
1694
  for (const location of locations) {
@@ -2739,12 +2763,17 @@ function isRecord(value: unknown): value is Record<string, unknown> {
2739
2763
  * What a plugin build keys a plugin input on: the digest of the files its build
2740
2764
  * reads from a source directory (`pluginSourceDigest`), or a file's bytes. An
2741
2765
  * input that cannot be read has a state of its own, so the change that made it
2742
- * unreadable is reported and the build names the failure.
2766
+ * unreadable is reported and the build names the failure. A directory's digest
2767
+ * comes from the load's record store while the selected files' metadata holds
2768
+ * there (#1728), and from the files otherwise.
2743
2769
  */
2744
- function pluginInputState(input: string): string {
2770
+ function pluginInputState(
2771
+ input: string,
2772
+ identities: PluginContentIdentities.Store | undefined,
2773
+ ): string {
2745
2774
  try {
2746
2775
  return WatchPaths.isDirectory(input)
2747
- ? `directory:${pluginSourceDigest(input)}`
2776
+ ? `directory:${PluginContentIdentities.sourceDirectory(identities, input)}`
2748
2777
  : `file:${fingerprintProjectInputFile(input)}`;
2749
2778
  } catch (error) {
2750
2779
  return `unreadable:${error instanceof Error ? error.message : String(error)}`;
@@ -1,27 +1,29 @@
1
1
  import crypto from "node:crypto";
2
- import fs from "node:fs";
3
2
  import path from "node:path";
4
3
 
5
4
  import { SidecarEnvironment } from "../../compiler/internal/sharedHost/SidecarEnvironment";
6
5
  import { runtimeExecutableIdentity } from "../../internal/runtimeExecutableIdentity";
6
+ import type { PluginContentIdentities } from "./source/PluginContentIdentities";
7
7
  import { SourceBuildCacheLayout } from "./source/SourceBuildCacheLayout";
8
8
  import { resolveSourceBuildCachePaths } from "./source/resolveSourceBuildCachePaths";
9
9
 
10
10
  /**
11
11
  * The on-disk format of the capability-resolution cache, shared by its reader
12
- * and writer so both agree on the entry's location, version tag, and how a
13
- * plugin source's metadata signature is taken. What proves an entry still
14
- * describes the project is the host inputs' states and the plugin sources'
15
- * states, each by the rule its producer applies.
12
+ * and writer so both agree on the entry's location and version tag. What proves
13
+ * an entry still describes the project is the host inputs' states and the
14
+ * plugin sources' states, each by the rule its producer applies; a plugin
15
+ * source's metadata signature and the clock reference it is judged against
16
+ * belong to `PluginContentIdentities`, which every cross-process plugin proof
17
+ * shares.
16
18
  *
17
- * @evidence contracts/common.md#principled-implementation Reader and writer share format/key and source-metadata witness rules. Metadata acceleration requires a matching device and older reported mtime, relying on native metadata/clock semantics rather than certifying arbitrary timestamp restoration or content mutation.
18
- * @evidence contracts/common.md#clear-and-simple-design The namespace owns format identity and witness construction, while entry acceptance and answer production stay with their reader/writer.
19
- * @evidence contracts/common.md#prohibited-implementation-shortcuts Version tags and NUL delimiters encode the persistence contract; unavailable clock/device evidence causes full content proof rather than guessed reuse.
20
- * @evidence contracts/common.md#meaningful-documentation Native JSDoc explains format evolution, unforgeable path separation and clock proof premises in separate paragraphs; function/tag spacing follows the documentation skill.
21
- * @evidence contracts/portability.md#os-neutral-implementation Native path joins, cache-root resolution and bigint stat device identities preserve OS-neutral filesystem behavior; clock witnesses are matched per device rather than assumed from platform names.
22
- * @evidence contracts/performance.md#efficient-algorithms Key construction orders and serializes environment/name bytes, streams runtime content and delegates native ancestor/manifest/cache-layout selection. Clock witnesses create/query/remove a probe, and each source witness queries native metadata and formats numeric text; path/native/text costs remain distinct from source-content proof owned by the reader.
23
- * @evidence contracts/performance.md#reuse-equivalent-work Project/version keys share one answer across requested capabilities; digest reuse additionally requires matching signature and device-clock separation.
24
- * @evidence contracts/performance.md#bound-retention-and-release-resources Clock probes are call-owned and removed in finally; cache entries belong to the source-cache pruning owner rather than a process-global answer collection. Failed probe removal can leave a small file and is best effort.
19
+ * @evidence contracts/common.md#principled-implementation Reader and writer share format/key rules; source-metadata witnesses come from the shared identity owner, so this cache and the build path apply one separable-stamp rule.
20
+ * @evidence contracts/common.md#clear-and-simple-design The namespace owns format identity and entry location, while entry acceptance, answer production and metadata witnesses stay with their owners.
21
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Version tags and NUL delimiters encode the persistence contract rather than guessed reuse.
22
+ * @evidence contracts/common.md#meaningful-documentation Native JSDoc explains format evolution, unforgeable path separation and the witness owner in separate paragraphs; function/tag spacing follows the documentation skill.
23
+ * @evidence contracts/portability.md#os-neutral-implementation Native path joins and cache-root resolution preserve OS-neutral filesystem behavior.
24
+ * @evidence contracts/performance.md#efficient-algorithms Key construction orders and serializes environment/name bytes, streams runtime content and delegates native ancestor/manifest/cache-layout selection.
25
+ * @evidence contracts/performance.md#reuse-equivalent-work Project/version keys share one answer across requested capabilities.
26
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Cache entries belong to the source-cache pruning owner; this namespace retains nothing.
25
27
  */
26
28
  export namespace CapabilityResolutionFormat {
27
29
  /**
@@ -106,6 +108,13 @@ export namespace CapabilityResolutionFormat {
106
108
 
107
109
  /** Effective environment selecting workspace-local or explicit storage. */
108
110
  env?: NodeJS.ProcessEnv;
111
+
112
+ /**
113
+ * Record store of the project's plugin cache. WARNING (#1725): without it
114
+ * every lookup streamed the whole runtime executable, several times per
115
+ * capability resolution, in every process.
116
+ */
117
+ identities?: PluginContentIdentities.Store;
109
118
  }): string | null {
110
119
  const environment = SidecarEnvironment.merge(options.env ?? process.env);
111
120
  const runtime =
@@ -115,7 +124,7 @@ export namespace CapabilityResolutionFormat {
115
124
  let root: string;
116
125
  let runtimeIdentity: string;
117
126
  try {
118
- const observed = runtimeExecutableIdentity(runtime);
127
+ const observed = runtimeExecutableIdentity(runtime, options.identities);
119
128
  if (observed === undefined) return null;
120
129
  runtimeIdentity = observed;
121
130
  root = resolveSourceBuildCachePaths(
@@ -152,97 +161,4 @@ export namespace CapabilityResolutionFormat {
152
161
  `${key}.json`,
153
162
  );
154
163
  }
155
-
156
- /**
157
- * A clock reference for plugin source signatures, minted by a write of the
158
- * cache's own beside the entry: the modification stamp a fresh probe file got
159
- * there, by the device that reported it.
160
- *
161
- * A stamp strictly older than the same device's fresh reference satisfies
162
- * this cache's separation policy. Reuse still assumes the filesystem's
163
- * reported metadata reflects writes; the comparison cannot certify arbitrary
164
- * timestamp restoration or future clock behavior. The probe is the cache's
165
- * own file, named for this call, and removal is attempted at once, so
166
- * concurrent readers never lend each other a reference. A failed write, or a
167
- * plugin source on another device, leaves nothing separable, and the proof
168
- * reads the files.
169
- *
170
- * @param entry The entry file, whose directory the probe is written in.
171
- * @evidence contracts/common.md#principled-implementation A unique freshly-written file supplies its native device/mtime reference; acquisition failure clears the witness, and the source observer requires a matching device and strictly older reported stamp under the metadata policy.
172
- * @evidence contracts/common.md#clear-and-simple-design The function returns only device-to-stamp evidence and owns its transient probe from creation through cleanup.
173
- * @evidence contracts/common.md#prohibited-implementation-shortcuts Random probe identity avoids borrowing a concurrent call's witness; failed acquisition does not substitute wall-clock guesses for device evidence.
174
- * @evidence contracts/common.md#meaningful-documentation Native JSDoc explains ended ticks, cross-device refusal and probe lifetime in separate paragraphs; tag separation follows the documentation skill.
175
- * @evidence contracts/portability.md#os-neutral-implementation Native joins and bigint lstat use the actual filesystem's device/stamp values; no fixed timestamp precision or OS-specific temp path is assumed.
176
- * @evidence contracts/performance.md#efficient-algorithms Probe path/UUID/hrtime text construction and mkdir/write/lstat/removal queries produce a witness without reading plugin source bytes; native path/directory work remains and is not bounded by source count.
177
- *
178
- * @evidenceExclude contracts/performance.md#reuse-equivalent-work Each proof needs a fresh clock reference; sharing an earlier probe would defeat the rollback/separation premise.
179
- *
180
- * @evidence contracts/performance.md#bound-retention-and-release-resources The unique call-owned probe is removed in finally even after acquisition failure; removal is best effort and the returned map contains at most its single observed device.
181
- */
182
- export function clockReference(entry: string): ReadonlyMap<bigint, bigint> {
183
- const references = new Map<bigint, bigint>();
184
- const probe = path.join(
185
- path.dirname(entry),
186
- `clock-${process.pid}-${crypto.randomUUID()}.probe`,
187
- );
188
- try {
189
- fs.mkdirSync(path.dirname(probe), { recursive: true });
190
- fs.writeFileSync(probe, `${process.hrtime.bigint()}\n`);
191
- const stats = fs.lstatSync(probe, { bigint: true });
192
- references.set(stats.dev, stats.mtimeNs);
193
- } catch {
194
- references.clear();
195
- } finally {
196
- try {
197
- fs.rmSync(probe, { force: true });
198
- } catch {
199
- // A leftover probe contains this call's hrtime text, not source data.
200
- }
201
- }
202
- return references;
203
- }
204
-
205
- /**
206
- * One plugin source file's metadata signature, and whether its stamp is
207
- * separable from `reference`, for `pluginSourceFilesSignature`.
208
- *
209
- * The files the digest reads are regular files (`collectPluginSourceFiles`),
210
- * so these fields are the observer's metadata signature. Native metadata
211
- * reflecting a write/replacement is a reuse premise; the signature is not
212
- * itself a content hash or proof against arbitrary timestamp restoration.
213
- *
214
- * @param reference The clock reference minted for this proof.
215
- * @evidence contracts/common.md#principled-implementation lstat device/inode/link/mode/size/timestamps form the regular-file metadata signature; strict mtime-before-reference marks separation for a matching device under the cache's native metadata policy, without observing content.
216
- * @evidence contracts/common.md#clear-and-simple-design A closure carries one proof's device references and maps each requested file to signature/separation together.
217
- * @evidence contracts/common.md#prohibited-implementation-shortcuts Unreadable files return no witness, and unmatched device or same/newer stamps cannot authorize digest reuse.
218
- * @evidence contracts/common.md#meaningful-documentation Native JSDoc states the regular-file premise and reference origin, with separate acknowledgment prose following the documentation skill.
219
- * @evidence contracts/portability.md#os-neutral-implementation Metadata comes from native bigint lstat and actual device IDs; separators join numeric fields only and no host path is manually parsed.
220
- * @evidence contracts/performance.md#efficient-algorithms Each invocation performs native path/lstat work and formats seven bigint fields without reading content; fixed field count does not remove native lookup or numeric-text costs.
221
- * @evidence contracts/performance.md#reuse-equivalent-work The closure reuses one reference map across files in the same proof, while each file's current metadata is read anew.
222
- * @evidence contracts/performance.md#bound-retention-and-release-resources The closure retains the supplied reference map until its consumer releases it; clockReference supplies at most one entry, but arbitrary callers can supply larger or mutable maps. No file handle/global source population is retained and reference immutability is a caller premise.
223
- */
224
- export function sourceEvidence(
225
- reference: ReadonlyMap<bigint, bigint>,
226
- ): (file: string) => { separable: boolean; signature: string } | undefined {
227
- return (file) => {
228
- try {
229
- const stats = fs.lstatSync(file, { bigint: true });
230
- const minted = reference.get(stats.dev);
231
- return {
232
- separable: minted !== undefined && stats.mtimeNs < minted,
233
- signature: [
234
- stats.dev,
235
- stats.ino,
236
- stats.nlink,
237
- stats.mode,
238
- stats.size,
239
- stats.mtimeNs,
240
- stats.ctimeNs,
241
- ].join(":"),
242
- };
243
- } catch {
244
- return undefined;
245
- }
246
- };
247
- }
248
164
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
 
5
5
  import { SidecarEnvironment } from "../../../compiler/internal/sharedHost/SidecarEnvironment";
6
6
  import { runtimeExecutableIdentity } from "../../../internal/runtimeExecutableIdentity";
7
+ import type { PluginContentIdentities } from "../source/PluginContentIdentities";
7
8
  import { SourceBuildCacheLayout } from "../source/SourceBuildCacheLayout";
8
9
  import { recordCacheFileUse } from "../source/recordCacheFileUse";
9
10
  import { resolveSourceBuildCachePaths } from "../source/resolveSourceBuildCachePaths";
@@ -127,20 +128,26 @@ export namespace PluginDescriptorEvaluationCache {
127
128
 
128
129
  /** Ttsc build version, invalidating answers from another build. */
129
130
  version: string;
131
+
132
+ /**
133
+ * Record store proving the runtimes' bytes from metadata (#1723); without
134
+ * it each runtime is streamed again.
135
+ */
136
+ identities?: PluginContentIdentities.Store;
130
137
  }): string | null {
131
138
  const environment = SidecarEnvironment.merge(props.env);
132
139
  // Startup preloads can run before the descriptor recorder and read inputs
133
140
  // it never sees; a stable option string cannot prove their current meaning.
134
141
  if (SidecarEnvironment.read(environment, "NODE_OPTIONS")?.trim())
135
142
  return null;
136
- const runtime = runtimeIdentity(props.runtime);
143
+ const runtime = runtimeIdentity(props.runtime, props.identities);
137
144
  if (runtime === null) return null;
138
145
  const additionalRuntime =
139
146
  props.additionalRuntime === undefined
140
147
  ? undefined
141
148
  : props.additionalRuntime === props.runtime
142
149
  ? runtime
143
- : runtimeIdentity(props.additionalRuntime);
150
+ : runtimeIdentity(props.additionalRuntime, props.identities);
144
151
  if (additionalRuntime === null) return null;
145
152
  let root: string;
146
153
  try {
@@ -358,8 +365,11 @@ export namespace PluginDescriptorEvaluationCache {
358
365
  * content digest and bracketed lexical/physical metadata, so replaced bytes
359
366
  * cannot reuse an evaluation by restoring size/timestamps. Null is unproved.
360
367
  */
361
- function runtimeIdentity(runtime: string): string | null {
362
- return runtimeExecutableIdentity(runtime) ?? null;
368
+ function runtimeIdentity(
369
+ runtime: string,
370
+ identities: PluginContentIdentities.Store | undefined,
371
+ ): string | null {
372
+ return runtimeExecutableIdentity(runtime, identities) ?? null;
363
373
  }
364
374
 
365
375
  /**