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
@@ -6,6 +6,7 @@ import { GoEnvironmentReading } from "./GoEnvironmentReading";
6
6
  import { GoSourceInputs } from "./GoSourceInputs";
7
7
  import { GoToolResolution } from "./GoToolResolution";
8
8
  import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
9
+ import { PluginContentIdentities } from "./PluginContentIdentities";
9
10
  import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
10
11
  import { spawnGoTool } from "./spawnGoTool";
11
12
 
@@ -42,14 +43,17 @@ import { spawnGoTool } from "./spawnGoTool";
42
43
  * variable carries: the Go tool, the Go environment file `go env -w` writes,
43
44
  * the executables the C toolchain commands name, and GOROOT. A consumer that
44
45
  * keeps the reading compares their metadata before reusing it.
46
+ * @param identities Record store that lets a new process prove the GOROOT
47
+ * corpus and executable bytes from their metadata (`PluginContentIdentities`,
48
+ * #1722); without it every process reads them.
45
49
  * @evidence contracts/common.md#principled-implementation Fixed artifact flags, observed compiler bytes/version, selected reported-or-fallback build values, named command executables and selected SDK files enter the same framed identity used by source-state reporting. Fallback values and metadata-qualified reuse are explicit premises, not complete observation of arbitrary toolchain inputs.
46
50
  * @evidence contracts/common.md#clear-and-simple-design Private helpers separate compiler identity, Go-reported settings, external variables and SDK content while sharing one hash sink and optional pre-read witness.
47
51
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Cache signatures include real identity/change metadata and effective invocation context; failed SDK witnessing refuses reuse rather than accepting a VERSION-only proxy.
48
52
  * @evidence contracts/common.md#meaningful-documentation Native documentation names the input classes and witness purpose; private comments explain context-sensitive compiler memoization and SDK exclusions without treating a passing check as proof.
49
53
  * @evidence contracts/portability.md#os-neutral-implementation Node path/stat/process APIs resolve native tool identities; platform-specific executable suffixes and environment lookup are isolated in GoToolResolution, and Go supplies its own effective build settings.
50
54
  * @evidence contracts/performance.md#efficient-algorithms SDK traversal sorts reached entry names and selected file paths, performs native stat/realpath/link queries and serializes topology/metadata/path text. Matching manifests share aggregate hashes; changes read full selected files via the caller adapter, retaining file buffers with population/topology data. Compiler misses capture at most three complete version/byte readings when native metadata moves during a reading; only a stable attempt enters the memo. Go env can run up to three times for its file witness, and command tokens can each perform executable searches/full reads. Environment sorting/framing and command substring parsing also process name/value/command bytes; E/D/F/B alone do not capture native lookup or text comparison costs.
51
- * @evidence contracts/performance.md#reuse-equivalent-work Compiler memo identity includes selected path/file metadata and invocation cwd/environment; SDK aggregate reuse requires matching complete ordered metadata/topology. Both rely on native metadata distinguishability and sequential observation premises, not independently rehashed content on each hit. Physical directories expand once per selection policy while aliases retain edges; optional caller witnesses receive reached selected dependencies.
52
- * @evidence contracts/performance.md#bound-retention-and-release-resources Compiler, Go-environment-path and SDK maps retain historical distinct tool/context/root keys without eviction. SDK population/topology and full individual file reads contribute transient bytes; synchronous probes delegate process/capture lifetime to their owner and do not certify arbitrary descendants are gone. No independent entry/record/file/output byte ceiling is imposed here.
55
+ * @evidence contracts/performance.md#reuse-equivalent-work Compiler memo identity includes selected path/file metadata and invocation cwd/environment; SDK aggregate reuse requires matching complete ordered metadata/topology. With a record store, a new process reuses the SDK aggregate and executable digests only while their signatures match the recorded ones and every stamp is separable from a freshly minted reference (#1722); `go version` and `go env` stay live because they report toolchain selection no file metadata shows. All of this relies on native metadata distinguishability and sequential observation premises. Physical directories expand once per selection policy while aliases retain edges; optional caller witnesses receive reached selected dependencies.
56
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Compiler, Go-environment-path and SDK maps retain historical distinct tool/context/root keys without eviction; persisted records belong to the single-file collector. SDK population/topology and full individual file reads contribute transient bytes; synchronous probes delegate process/capture lifetime to their owner and do not certify arbitrary descendants are gone. No independent entry/record/file/output byte ceiling is imposed here.
53
57
  */
54
58
  export function hashPluginBuildEnvironment(
55
59
  hash: { update(data: string): unknown },
@@ -58,6 +62,7 @@ export function hashPluginBuildEnvironment(
58
62
  env: NodeJS.ProcessEnv,
59
63
  filesystem: SourceBuildFilesystemOperations,
60
64
  witness?: PluginBuildEnvironmentWitness.Record,
65
+ identities?: PluginContentIdentities.Store,
61
66
  ): void {
62
67
  // Fixed artifact policy must invalidate binaries and reported source states
63
68
  // together, including binaries cached before this policy was introduced.
@@ -68,11 +73,25 @@ export function hashPluginBuildEnvironment(
68
73
  hash.update(
69
74
  JSON.stringify([
70
75
  "go",
71
- resolveGoCompilerIdentity(goBinary, env, directory, witness),
76
+ resolveGoCompilerIdentity(
77
+ goBinary,
78
+ env,
79
+ directory,
80
+ witness,
81
+ identities,
82
+ ),
72
83
  ]),
73
84
  );
74
85
  }
75
- hashGoBuildEnvironment(hash, goBinary, directory, env, filesystem, witness);
86
+ hashGoBuildEnvironment(
87
+ hash,
88
+ goBinary,
89
+ directory,
90
+ env,
91
+ filesystem,
92
+ witness,
93
+ identities,
94
+ );
76
95
  hashExternalGoBuildEnvironment(hash, env);
77
96
  }
78
97
 
@@ -173,6 +192,7 @@ function resolveGoCompilerIdentity(
173
192
  env: NodeJS.ProcessEnv = process.env,
174
193
  cwd: string = process.cwd(),
175
194
  witness?: PluginBuildEnvironmentWitness.Record,
195
+ identities?: PluginContentIdentities.Store,
176
196
  ): string {
177
197
  const selected = GoToolResolution.resolveGoToolForBuild(
178
198
  goBinary,
@@ -217,7 +237,13 @@ function resolveGoCompilerIdentity(
217
237
  compilerEnv,
218
238
  cwd,
219
239
  );
220
- identity = computeGoCompilerIdentity(selected, resolved, compilerEnv, cwd);
240
+ identity = computeGoCompilerIdentity(
241
+ selected,
242
+ resolved,
243
+ compilerEnv,
244
+ cwd,
245
+ identities,
246
+ );
221
247
  if (!PluginBuildEnvironmentWitness.holds(observed)) continue;
222
248
  for (const [file, signature] of observed)
223
249
  if (witness !== undefined && !witness.has(file))
@@ -269,10 +295,13 @@ function computeGoCompilerIdentity(
269
295
  resolved: string,
270
296
  env: NodeJS.ProcessEnv,
271
297
  cwd: string,
298
+ identities: PluginContentIdentities.Store | undefined,
272
299
  ): string {
273
300
  if (!fs.existsSync(resolved)) {
274
301
  return "missing";
275
302
  }
303
+ // `go version` stays a live probe: it answers which toolchain the effective
304
+ // environment selects (GOTOOLCHAIN, go.mod), which no file metadata shows.
276
305
  const version = spawnGoTool(goBinary, ["version"], {
277
306
  cwd,
278
307
  encoding: "utf8",
@@ -283,10 +312,27 @@ function computeGoCompilerIdentity(
283
312
  version.error !== undefined
284
313
  ? ((version.error as NodeJS.ErrnoException).code ?? version.error.message)
285
314
  : `${version.status ?? 0}:${version.stdout}${version.stderr}`;
286
- const binaryHash = hashFile(resolved);
315
+ const binaryHash = hashExecutable(resolved, identities);
287
316
  return `sha256:${binaryHash}:${versionText}`;
288
317
  }
289
318
 
319
+ /**
320
+ * The content digest of an executable, proven from its metadata in a process
321
+ * that has the record store (#1722) and read in full otherwise.
322
+ */
323
+ function hashExecutable(
324
+ file: string,
325
+ identities: PluginContentIdentities.Store | undefined,
326
+ ): string {
327
+ return PluginContentIdentities.digest(
328
+ identities,
329
+ "executable",
330
+ file,
331
+ (references) => PluginContentIdentities.fileObservation(references, file),
332
+ () => hashFile(file),
333
+ );
334
+ }
335
+
290
336
  function resolveExecutableIdentityPath(
291
337
  binary: string,
292
338
  env: NodeJS.ProcessEnv = process.env,
@@ -352,7 +398,8 @@ function hashGoBuildEnvironment(
352
398
  cwd: string,
353
399
  env: NodeJS.ProcessEnv,
354
400
  filesystem: SourceBuildFilesystemOperations,
355
- witness?: PluginBuildEnvironmentWitness.Record,
401
+ witness: PluginBuildEnvironmentWitness.Record | undefined,
402
+ identities: PluginContentIdentities.Store | undefined,
356
403
  ): void {
357
404
  const values = resolveGoBuildEnvironment(
358
405
  goBinary,
@@ -360,6 +407,7 @@ function hashGoBuildEnvironment(
360
407
  env,
361
408
  filesystem,
362
409
  witness,
410
+ identities,
363
411
  );
364
412
  for (const key of GO_BUILD_ENV_KEYS) {
365
413
  const value = values.get(key);
@@ -374,7 +422,8 @@ function resolveGoBuildEnvironment(
374
422
  cwd: string,
375
423
  env: NodeJS.ProcessEnv,
376
424
  filesystem: SourceBuildFilesystemOperations,
377
- witness?: PluginBuildEnvironmentWitness.Record,
425
+ witness: PluginBuildEnvironmentWitness.Record | undefined,
426
+ identities: PluginContentIdentities.Store | undefined,
378
427
  ): Map<string, string> {
379
428
  const values = new Map<string, string>();
380
429
  if (goBinary !== undefined) {
@@ -384,6 +433,7 @@ function resolveGoBuildEnvironment(
384
433
  env,
385
434
  GO_BUILD_ENV_KEYS,
386
435
  witness,
436
+ identities,
387
437
  );
388
438
  if (parsed !== undefined) {
389
439
  try {
@@ -392,7 +442,14 @@ function resolveGoBuildEnvironment(
392
442
  if (typeof raw === "string" && raw !== "") {
393
443
  values.set(
394
444
  key,
395
- normalizeGoBuildEnvValue(key, raw, env, filesystem, witness),
445
+ normalizeGoBuildEnvValue(
446
+ key,
447
+ raw,
448
+ env,
449
+ filesystem,
450
+ witness,
451
+ identities,
452
+ ),
396
453
  );
397
454
  }
398
455
  }
@@ -408,7 +465,14 @@ function resolveGoBuildEnvironment(
408
465
  if (value !== undefined && value !== "") {
409
466
  values.set(
410
467
  key,
411
- normalizeGoBuildEnvValue(key, value, env, filesystem, witness),
468
+ normalizeGoBuildEnvValue(
469
+ key,
470
+ value,
471
+ env,
472
+ filesystem,
473
+ witness,
474
+ identities,
475
+ ),
412
476
  );
413
477
  }
414
478
  }
@@ -420,7 +484,8 @@ function normalizeGoBuildEnvValue(
420
484
  value: string,
421
485
  env: NodeJS.ProcessEnv,
422
486
  filesystem: SourceBuildFilesystemOperations,
423
- witness?: PluginBuildEnvironmentWitness.Record,
487
+ witness: PluginBuildEnvironmentWitness.Record | undefined,
488
+ identities: PluginContentIdentities.Store | undefined,
424
489
  ): string {
425
490
  if (key === "GOROOT") {
426
491
  // Root/version observations also cover a missing SDK. The complete SDK
@@ -428,10 +493,15 @@ function normalizeGoBuildEnvValue(
428
493
  // nested content changes that do not move the root or VERSION metadata.
429
494
  PluginBuildEnvironmentWitness.add(witness, value);
430
495
  PluginBuildEnvironmentWitness.add(witness, path.join(value, "VERSION"));
431
- return resolveGoRootCacheIdentity(value, filesystem, witness);
496
+ return resolveGoRootCacheIdentity(value, filesystem, witness, identities);
432
497
  }
433
498
  if (GO_BUILD_COMMAND_ENV_KEYS.has(key)) {
434
- return `${value}\0${resolveCommandCacheIdentity(value, env, witness)}`;
499
+ return `${value}\0${resolveCommandCacheIdentity(
500
+ value,
501
+ env,
502
+ witness,
503
+ identities,
504
+ )}`;
435
505
  }
436
506
  return value;
437
507
  }
@@ -451,7 +521,8 @@ function normalizeGoBuildEnvValue(
451
521
  function resolveCommandCacheIdentity(
452
522
  command: string,
453
523
  env: NodeJS.ProcessEnv,
454
- witness?: PluginBuildEnvironmentWitness.Record,
524
+ witness: PluginBuildEnvironmentWitness.Record | undefined,
525
+ identities: PluginContentIdentities.Store | undefined,
455
526
  ): string {
456
527
  const tokens = splitGoCommand(command);
457
528
  if (tokens === null) {
@@ -473,7 +544,7 @@ function resolveCommandCacheIdentity(
473
544
  }
474
545
  let identity: string;
475
546
  try {
476
- identity = `command:sha256:${hashFile(resolved)}`;
547
+ identity = `command:sha256:${hashExecutable(resolved, identities)}`;
477
548
  } catch {
478
549
  return `command:unreadable:${resolved}`;
479
550
  }
@@ -487,7 +558,7 @@ function resolveCommandCacheIdentity(
487
558
  if (!GoToolResolution.isExecutableFile(operand)) return;
488
559
  PluginBuildEnvironmentWitness.add(witness, operand);
489
560
  try {
490
- identity += `;${index + 1}:sha256:${hashFile(operand)}`;
561
+ identity += `;${index + 1}:sha256:${hashExecutable(operand, identities)}`;
491
562
  } catch {
492
563
  identity += `;${index + 1}:unreadable:${operand}`;
493
564
  }
@@ -544,6 +615,9 @@ interface GoRootIdentitySnapshot {
544
615
  files: string[];
545
616
  topology: string[];
546
617
  signature: string;
618
+
619
+ /** Whether every file stamp precedes its device's minted reference. */
620
+ separable: boolean;
547
621
  }
548
622
 
549
623
  interface GoRootIdentityCacheEntry {
@@ -554,19 +628,31 @@ interface GoRootIdentityCacheEntry {
554
628
  // GOROOT is usually stable but its selected source/tool payloads contribute to
555
629
  // every plugin key. Retain only the final aggregate
556
630
  // identity, guarded by a fresh metadata/topology manifest on every call. A
557
- // changed or incomplete manifest falls through to a full content read.
631
+ // changed or incomplete manifest falls through to the record store, then to a
632
+ // full content read.
633
+ //
634
+ // WARNING (#1186, #1722): this map lives only as long as the process. #1186
635
+ // stopped at it, so every CLI launch, worker and bundler restart still read the
636
+ // whole SDK (3,723 files, 135 MB for the bundled toolchain). The record store
637
+ // below is what carries the identity across processes; never drop it in favor
638
+ // of this memo alone.
558
639
  const goRootIdentityCache = new Map<string, GoRootIdentityCacheEntry>();
559
640
 
560
641
  function resolveGoRootCacheIdentity(
561
642
  goRoot: string,
562
643
  filesystem: SourceBuildFilesystemOperations,
563
- witness?: PluginBuildEnvironmentWitness.Record,
644
+ witness: PluginBuildEnvironmentWitness.Record | undefined,
645
+ identities: PluginContentIdentities.Store | undefined,
564
646
  ): string {
565
647
  const resolved = resolveRealPath(goRoot);
566
648
  if (!fs.existsSync(resolved)) {
567
649
  return `missing:${goRoot}`;
568
650
  }
569
- const snapshot = collectGoRootIdentitySnapshot(resolved, witness);
651
+ const snapshot = collectGoRootIdentitySnapshot(
652
+ resolved,
653
+ witness,
654
+ identities?.references,
655
+ );
570
656
  if (!snapshot.complete)
571
657
  PluginBuildEnvironmentWitness.refuse(witness, resolved);
572
658
  if (snapshot.complete) {
@@ -575,15 +661,37 @@ function resolveGoRootCacheIdentity(
575
661
  return cached.identity;
576
662
  }
577
663
  }
578
- const hash = crypto.createHash("sha256");
579
- hash.update(JSON.stringify(snapshot.topology));
580
- for (const file of snapshot.files) {
581
- const relative = path.relative(resolved, file).split(path.sep).join("/");
582
- const contents = filesystem.readFile(file);
583
- hash.update(JSON.stringify([relative, contents.length]));
584
- hash.update(contents);
585
- }
586
- const identity = `sha256:${hash.digest("hex")}`;
664
+ // The first observation is the snapshot the witness just recorded; a second
665
+ // one, after the content read, brackets that read for publication.
666
+ let observed = 0;
667
+ const identity = PluginContentIdentities.digest(
668
+ snapshot.complete ? identities : undefined,
669
+ "goroot",
670
+ resolved,
671
+ (references) => {
672
+ const current =
673
+ observed++ === 0
674
+ ? snapshot
675
+ : collectGoRootIdentitySnapshot(resolved, undefined, references);
676
+ return current.complete
677
+ ? { separable: current.separable, signature: current.signature }
678
+ : undefined;
679
+ },
680
+ () => {
681
+ const hash = crypto.createHash("sha256");
682
+ hash.update(JSON.stringify(snapshot.topology));
683
+ for (const file of snapshot.files) {
684
+ const relative = path
685
+ .relative(resolved, file)
686
+ .split(path.sep)
687
+ .join("/");
688
+ const contents = filesystem.readFile(file);
689
+ hash.update(JSON.stringify([relative, contents.length]));
690
+ hash.update(contents);
691
+ }
692
+ return `sha256:${hash.digest("hex")}`;
693
+ },
694
+ );
587
695
  if (snapshot.complete) {
588
696
  goRootIdentityCache.set(resolved, {
589
697
  identity,
@@ -596,9 +704,10 @@ function resolveGoRootCacheIdentity(
596
704
  function collectGoRootIdentitySnapshot(
597
705
  root: string,
598
706
  witness?: PluginBuildEnvironmentWitness.Record,
707
+ references?: ReadonlyMap<bigint, bigint>,
599
708
  ): GoRootIdentitySnapshot {
600
709
  const out: string[] = [];
601
- const state = { complete: true };
710
+ const state = { complete: true, separable: references !== undefined };
602
711
  const topology: string[] = [];
603
712
  walkGoRootIdentity(root, out, topology, state, witness);
604
713
  out.sort();
@@ -613,6 +722,11 @@ function collectGoRootIdentitySnapshot(
613
722
  state.complete = false;
614
723
  continue;
615
724
  }
725
+ if (
726
+ references === undefined ||
727
+ !PluginContentIdentities.separable(references, stats)
728
+ )
729
+ state.separable = false;
616
730
  signature.update(
617
731
  [
618
732
  relative,
@@ -634,6 +748,7 @@ function collectGoRootIdentitySnapshot(
634
748
  files: out,
635
749
  topology,
636
750
  signature: signature.digest("hex"),
751
+ separable: state.separable,
637
752
  };
638
753
  }
639
754
 
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
 
4
4
  import { GoToolResolution } from "./GoToolResolution";
5
5
  import type { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
6
+ import type { PluginContentIdentities } from "./PluginContentIdentities";
6
7
  import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
7
8
  import { resolveGoCompiler } from "./resolveGoCompiler";
8
9
 
@@ -19,12 +20,16 @@ import { resolveGoCompiler } from "./resolveGoCompiler";
19
20
  * GOROOT metadata. Witnessing a newly selected environment file can repeat the
20
21
  * probe to observe it before reading. Compiler identity reuses a
21
22
  * metadata-and-context memo; a changed SDK manifest rehashes all contributing
22
- * content rather than only edited files.
23
+ * content rather than only edited files. A supplied record store lets a new
24
+ * process take the SDK and executable digests from records instead of their
25
+ * bytes, while `go env` and `go version` still run.
23
26
  *
24
27
  * @param directory The directory a build runs `go` in.
25
28
  * @param env The effective environment, `process.env` by default.
26
29
  * @param witness Receives the paths the reading depends on and no variable
27
30
  * carries (`hashPluginBuildEnvironment`).
31
+ * @param identities Record store that lets a new process prove the SDK and
32
+ * executables from metadata (#1722).
28
33
  * @evidence contracts/common.md#principled-implementation The digest uses the same resolved compiler and environment serializer as the binary key, so reported toolchain state denotes the inputs actually used by the build.
29
34
  * @evidence contracts/common.md#clear-and-simple-design Compiler selection, executable resolution and hashing each stay with their owning helper; this function only composes one environment reading.
30
35
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The result comes from real Go/environment inputs, not a fixed compiler version or a caller-specific substitute.
@@ -32,13 +37,14 @@ import { resolveGoCompiler } from "./resolveGoCompiler";
32
37
  * @evidence contracts/portability.md#os-neutral-implementation Executable identity is resolved before probing, and Node filesystem/process boundaries implement native spelling rather than assuming Windows or POSIX paths are interchangeable.
33
38
  * @evidence contracts/performance.md#efficient-algorithms The wrapper resolves the compiler/tool under the supplied environment before delegating full environment hashing. Native resolution/probes, SDK metadata/name/path sorting and changed-manifest full content reads contribute cost; the final hash output is fixed-width but streamed identity/input text and full-file buffers are not. No independent duplicate source-directory digest scan is added here.
34
39
  *
35
- * @evidenceExclude contracts/performance.md#reuse-equivalent-work This is the fresh-reading boundary required by builds; processPluginBuildEnvironment owns reuse when a consumer permits it.
40
+ * @evidence contracts/performance.md#reuse-equivalent-work This is the fresh-reading boundary required by builds: the Go probes run on every call. Content digests of the SDK and executables are shared across processes only through the supplied record store's separable-signature proof; processPluginBuildEnvironment owns reuse of the whole reading when a consumer permits it.
36
41
  * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Its digest and filesystem adapter are call-local; retained compiler/content memo ownership belongs to the hasher.
37
42
  */
38
43
  export function pluginBuildEnvironment(
39
44
  directory: string,
40
45
  env: NodeJS.ProcessEnv = process.env,
41
46
  witness?: PluginBuildEnvironmentWitness.Record,
47
+ identities?: PluginContentIdentities.Store,
42
48
  ): string {
43
49
  const goBinary = GoToolResolution.resolveGoToolForBuild(
44
50
  resolveGoCompiler(env).binary,
@@ -54,6 +60,7 @@ export function pluginBuildEnvironment(
54
60
  env,
55
61
  { readFile: (location) => fs.readFileSync(location) },
56
62
  witness,
63
+ identities,
57
64
  );
58
65
  return hash.digest("hex");
59
66
  }
@@ -2,24 +2,40 @@ import { parentPort } from "node:worker_threads";
2
2
 
3
3
  import { serializeCompilerError } from "../../../internal/serializeCompilerError";
4
4
  import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
5
+ import { PluginContentIdentities } from "./PluginContentIdentities";
5
6
  import { pluginBuildEnvironment } from "./pluginBuildEnvironment";
6
7
 
7
8
  /**
8
9
  * Observe one requested native toolchain environment on the exclusive worker.
9
10
  * Explicit environment values reach every Go/toolchain reader; no caller
10
11
  * process globals are written. Only actual readings and pre-read witnesses
11
- * cross the worker boundary, and failures retain their real error details.
12
+ * cross the worker boundary, and failures retain their real error details. A
13
+ * request that names its project lets this isolate prove the SDK and the
14
+ * executables from the plugin cache's records instead of their bytes (#1722).
12
15
  */
13
16
  parentPort?.on(
14
17
  "message",
15
- (request: { directory: string; env: NodeJS.ProcessEnv }) => {
18
+ (request: {
19
+ directory: string;
20
+ env: NodeJS.ProcessEnv;
21
+ projectRoot?: string;
22
+ }) => {
16
23
  try {
17
24
  const witness: PluginBuildEnvironmentWitness.Record = new Map();
25
+ const identities =
26
+ request.projectRoot === undefined
27
+ ? undefined
28
+ : PluginContentIdentities.open({
29
+ projectRoot: request.projectRoot,
30
+ env: request.env,
31
+ sources: [request.directory],
32
+ });
18
33
  parentPort!.postMessage({
19
34
  environment: pluginBuildEnvironment(
20
35
  request.directory,
21
36
  request.env,
22
37
  witness,
38
+ identities,
23
39
  ),
24
40
  witness,
25
41
  });
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
 
5
+ import { ttscPackageVersion } from "./ttscPackageVersion";
6
+
5
7
  /**
6
8
  * Package-version labels used by plugin build keys (`computeCacheKey`) and
7
9
  * persisted transform identities. These are metadata inputs, not a measurement
@@ -21,42 +23,19 @@ import path from "node:path";
21
23
  * @param projectRoot The project base used to resolve
22
24
  * `typescript/package.json`.
23
25
  * @evidence contracts/common.md#principled-implementation Package-version labels supply the same metadata tuple used by plugin keys; absent/unreadable metadata receives the documented unknown/default label rather than an executable identity certificate.
24
- * @evidence contracts/common.md#clear-and-simple-design Host-version and project-version readers are separate because one installation is stable within the process while project resolution varies per call.
26
+ * @evidence contracts/common.md#clear-and-simple-design The host label comes from its own owner (`ttscPackageVersion`), which a reader needing only that label calls without this function's project resolution; the project-version reader is separate because project resolution varies per call.
25
27
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Node package resolution determines the actual project dependency; no particular project or fixture version is embedded.
26
28
  * @evidence contracts/common.md#meaningful-documentation Native prose explains persisted-output consumers, metadata fallbacks and the process-stable host premise without claiming that version labels establish executable or output equivalence.
27
29
  * @evidence contracts/portability.md#os-neutral-implementation Node createRequire and path APIs locate package metadata using native module resolution without manual separator or case normalization.
28
30
  * @evidence contracts/performance.md#efficient-algorithms Each call performs project package resolution and, when successful, reads/parses its manifest bytes; module search/path work and metadata size are not fixed by one logical manifest lookup. The host manifest is attempted only on the first invocation, including native path and JSON costs.
29
31
  * @evidence contracts/performance.md#reuse-equivalent-work All callers share the host installation's process-stable version; the project compiler version is resolved afresh so different project roots do not share a false identity.
30
- * @evidence contracts/performance.md#bound-retention-and-release-resources This helper retains one host-version label for the process lifetime, whose text follows its manifest value rather than a fixed byte limit; the helper's retained label count is independent of project count. Native module-resolution caches belong to Node, and synchronous file reads leave no persistent descriptor here.
32
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The host-version owner retains one label for the process lifetime, whose text follows its manifest value rather than a fixed byte limit; the retained label count is independent of project count. Native module-resolution caches belong to Node, and synchronous file reads leave no persistent descriptor here.
31
33
  */
32
34
  export function pluginBuildVersions(projectRoot: string): {
33
35
  tsgo: string;
34
36
  ttsc: string;
35
37
  } {
36
- return { tsgo: readTsgoVersion(projectRoot), ttsc: readTtscVersion() };
37
- }
38
-
39
- let cachedTtscVersion: string | null = null;
40
-
41
- function readTtscVersion(): string {
42
- if (cachedTtscVersion !== null) return cachedTtscVersion;
43
- try {
44
- const file = path.resolve(
45
- __dirname,
46
- "..",
47
- "..",
48
- "..",
49
- "..",
50
- "package.json",
51
- );
52
- const pkg = JSON.parse(fs.readFileSync(file, "utf8")) as {
53
- version?: string;
54
- };
55
- cachedTtscVersion = pkg.version ?? "0.0.0";
56
- } catch {
57
- cachedTtscVersion = "0.0.0";
58
- }
59
- return cachedTtscVersion;
38
+ return { tsgo: readTsgoVersion(projectRoot), ttsc: ttscPackageVersion() };
60
39
  }
61
40
 
62
41
  function readTsgoVersion(projectRoot: string): string {
@@ -1,8 +1,9 @@
1
1
  import crypto from "node:crypto";
2
2
 
3
+ import { PluginContentIdentities } from "./PluginContentIdentities";
3
4
  import { pluginBuildEnvironment } from "./pluginBuildEnvironment";
4
- import { pluginSourceDigest } from "./pluginSourceDigest";
5
5
  import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
6
+ import { provenPluginSourceDigest } from "./provenPluginSourceDigest";
6
7
 
7
8
  /**
8
9
  * The state of one plugin source directory as a plugin binary is built from it:
@@ -25,7 +26,9 @@ import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
25
26
  * process's own environment the environment is read once per directory and set
26
27
  * of variables (`processPluginBuildEnvironment`), and a proof that must not
27
28
  * accept a stale one compares through `pluginSourceStateHolds`, which reads it
28
- * again before it refutes a state.
29
+ * again before it refutes a state. A caller that names its project proves the
30
+ * sources and the SDK from that project's plugin cache records instead of
31
+ * reading them in every new process (#1725).
29
32
  *
30
33
  * @param directory The source directory.
31
34
  * @param options.env An effective environment other than this process's, which
@@ -34,6 +37,7 @@ import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
34
37
  * caller already read it.
35
38
  * @param options.environment The directory's `pluginBuildEnvironment`, when the
36
39
  * caller already read it, as a plugin build's key does.
40
+ * @param options.projectRoot Project whose plugin cache root holds the records.
37
41
  * @returns The state, as lowercase hex.
38
42
  * @throws When a listed source file cannot be read, as the build itself would.
39
43
  * @evidence contracts/common.md#principled-implementation The state combines source and environment digests because both affect compiled plugin behavior; supplied readings preserve the exact inputs a build already keyed on.
@@ -42,7 +46,7 @@ import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
42
46
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain module-root versus overlay environment meaning and the provenance required for supplied digests.
43
47
  * @evidence contracts/portability.md#os-neutral-implementation Native source and toolchain identity are delegated to the shared filesystem/process readers; the final digest is platform-independent serialized text.
44
48
  * @evidence contracts/performance.md#efficient-algorithms Composition serializes/hashes supplied digest strings whose public inputs are not length-validated; owning generated digests are fixed-width hex. Missing readings delegate native source enumeration/full reads and environment variable/witness/tool/SDK work, without establishing an unmeasured dominant cost ranking.
45
- * @evidence contracts/performance.md#reuse-equivalent-work Supplied source/environment strings are trusted producer readings, not independently revalidated here. Process-environment memos require their owner's variable/path witness checks; explicit injected environments are read fresh. Equality inherits the selected-source and native metadata premises.
49
+ * @evidence contracts/performance.md#reuse-equivalent-work Supplied source/environment strings are trusted producer readings, not independently revalidated here. Process-environment memos require their owner's variable/path witness checks; explicit injected environments are read fresh. A named project lets missing source and SDK readings come from its record store across processes. Equality inherits the selected-source and native metadata premises.
46
50
  *
47
51
  * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources This function retains no resource or state after returning; process-environment memo lifetime belongs to its separate owner.
48
52
  */
@@ -51,19 +55,31 @@ export function pluginSourceState(
51
55
  options: {
52
56
  env?: NodeJS.ProcessEnv;
53
57
  environment?: string;
58
+ projectRoot?: string;
54
59
  sourceDigest?: string;
55
60
  } = {},
56
61
  ): string {
62
+ const sourceDigest =
63
+ options.sourceDigest ??
64
+ provenPluginSourceDigest(directory, options.projectRoot, options.env);
65
+ const environment =
66
+ options.environment ??
67
+ (options.env === undefined
68
+ ? processPluginBuildEnvironment(directory, false, options.projectRoot)
69
+ : pluginBuildEnvironment(
70
+ directory,
71
+ options.env,
72
+ undefined,
73
+ options.projectRoot === undefined
74
+ ? undefined
75
+ : PluginContentIdentities.open({
76
+ projectRoot: options.projectRoot,
77
+ env: options.env,
78
+ sources: [directory],
79
+ }),
80
+ ));
57
81
  return crypto
58
82
  .createHash("sha256")
59
- .update(
60
- `source=${options.sourceDigest ?? pluginSourceDigest(directory)}\n` +
61
- `environment=${
62
- options.environment ??
63
- (options.env === undefined
64
- ? processPluginBuildEnvironment(directory)
65
- : pluginBuildEnvironment(directory, options.env))
66
- }\n`,
67
- )
83
+ .update(`source=${sourceDigest}\nenvironment=${environment}\n`)
68
84
  .digest("hex");
69
85
  }
@@ -1,6 +1,6 @@
1
- import { pluginSourceDigest } from "./pluginSourceDigest";
2
1
  import { pluginSourceState } from "./pluginSourceState";
3
2
  import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
3
+ import { provenPluginSourceDigest } from "./provenPluginSourceDigest";
4
4
 
5
5
  /**
6
6
  * Whether one plugin source directory still holds the state a transform
@@ -16,12 +16,16 @@ import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
16
16
  * is refuted. A compile keys its binaries on a fresh read, so a state it
17
17
  * reported after a change gets one comparison with a refreshed environment.
18
18
  * That retry does not guarantee equality for a changing or differently keyed
19
- * environment, and a supplied source digest remains the caller's premise.
19
+ * environment, and a supplied source digest remains the caller's premise. A
20
+ * caller that names its project proves the sources and the SDK from that
21
+ * project's plugin cache records instead of reading them in every new process
22
+ * (#1725).
20
23
  *
21
24
  * @param directory The source directory, as the envelope names it.
22
25
  * @param state The state the envelope reported.
23
26
  * @param options.sourceDigest The directory's `pluginSourceDigest`, when the
24
27
  * caller holds one it can vouch for.
28
+ * @param options.projectRoot Project whose plugin cache root holds the records.
25
29
  * @throws When a listed source file cannot be read, as the build itself would.
26
30
  * @evidence contracts/common.md#principled-implementation Equality uses shared state composition and one real environment refresh after mismatch. This allows changed observations to be reconsidered without certifying the second comparison must match; supplied digest validity and native witness premises remain with their owners.
27
31
  * @evidence contracts/common.md#clear-and-simple-design One source reading is passed through both attempts and only the environment owner performs the refresh.
@@ -29,17 +33,20 @@ import { processPluginBuildEnvironment } from "./processPluginBuildEnvironment";
29
33
  * @evidence contracts/common.md#meaningful-documentation The prose states source-digest provenance, environment invalidation and the reason a mismatching state gets a fresh comparison.
30
34
  * @evidence contracts/portability.md#os-neutral-implementation Source reads and Go/environment witnesses use the same native boundary implementations as builds, rather than comparing OS names or guessed installation paths.
31
35
  * @evidence contracts/performance.md#efficient-algorithms At most one source digest computation is requested and reused by both comparisons; supplied strings bypass it. Native enumeration/file bytes, full environment identity/witness checks, optional tool/SDK refresh and digest-string hashing contribute work; mismatch repeats environment resolution once rather than bounding total cost by comparison count.
32
- * @evidence contracts/performance.md#reuse-equivalent-work A caller's proven source digest and the process owner's still-valid environment are reused; a mismatch forces the owning environment refresh before returning false.
36
+ * @evidence contracts/performance.md#reuse-equivalent-work A caller's proven source digest and the process owner's still-valid environment are reused; a mismatch forces the owning environment refresh before returning false. A named project lets the source and SDK readings come from its record store across processes.
33
37
  *
34
38
  * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The proof owns no retained source buffer, process or watcher; its caller owns any saved source digest.
35
39
  */
36
40
  export function pluginSourceStateHolds(
37
41
  directory: string,
38
42
  state: string,
39
- options: { sourceDigest?: string } = {},
43
+ options: { projectRoot?: string; sourceDigest?: string } = {},
40
44
  ): boolean {
41
- const sourceDigest = options.sourceDigest ?? pluginSourceDigest(directory);
42
- if (pluginSourceState(directory, { sourceDigest }) === state) return true;
43
- processPluginBuildEnvironment(directory, true);
44
- return pluginSourceState(directory, { sourceDigest }) === state;
45
+ const projectRoot = options.projectRoot;
46
+ const sourceDigest =
47
+ options.sourceDigest ?? provenPluginSourceDigest(directory, projectRoot);
48
+ if (pluginSourceState(directory, { projectRoot, sourceDigest }) === state)
49
+ return true;
50
+ processPluginBuildEnvironment(directory, true, projectRoot);
51
+ return pluginSourceState(directory, { projectRoot, sourceDigest }) === state;
45
52
  }