ttsc 0.31.0 → 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 (155) hide show
  1. package/cmd/platform/main.go +2 -2
  2. package/internal/sourceprocess/contain_windows.go +10 -13
  3. package/internal/sourceprocess/started_windows.go +41 -0
  4. package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
  5. package/internal/watchhelper/run.go +30 -20
  6. package/internal/watchhelper/run_other.go +4 -4
  7. package/internal/watchhelper/run_windows.go +471 -0
  8. package/internal/watchhelper/watchhelper.go +8 -2
  9. package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
  10. package/lib/compiler/internal/build/BuildExecution.d.ts +4 -2
  11. package/lib/compiler/internal/build/BuildExecution.js +40 -12
  12. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  13. package/lib/compiler/internal/startResidentTransform.js +5 -4
  14. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  15. package/lib/compiler/internal/transformProjectInMemory.js +9 -7
  16. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  17. package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
  18. package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
  19. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  20. package/lib/internal/nativeBinary.d.ts +3 -3
  21. package/lib/internal/nativeBinary.js +3 -3
  22. package/lib/internal/pluginSource.d.ts +1 -0
  23. package/lib/internal/pluginSource.js +1 -0
  24. package/lib/internal/pluginSource.js.map +1 -1
  25. package/lib/internal/resolveNodeBinary.d.ts +6 -1
  26. package/lib/internal/resolveNodeBinary.js +6 -2
  27. package/lib/internal/resolveNodeBinary.js.map +1 -1
  28. package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
  29. package/lib/internal/runtimeExecutableIdentity.js +192 -102
  30. package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
  31. package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
  32. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  33. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
  34. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
  35. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
  36. package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
  37. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  38. package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
  39. package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
  40. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  41. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
  42. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
  43. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  44. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +24 -14
  45. package/lib/plugin/internal/load/loadProjectPlugins.js +141 -56
  46. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  47. package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
  48. package/lib/plugin/internal/readCapabilityResolution.js +8 -5
  49. package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
  50. package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +48 -0
  51. package/lib/plugin/internal/source/GoEnvironmentReading.js +156 -0
  52. package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
  53. package/lib/plugin/internal/source/NativeSourcePackages.d.ts +51 -13
  54. package/lib/plugin/internal/source/NativeSourcePackages.js +182 -27
  55. package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
  56. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +42 -3
  57. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +68 -7
  58. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  59. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
  60. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
  61. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
  62. package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
  63. package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
  64. package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
  65. package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
  66. package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
  67. package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
  68. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
  69. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
  70. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  71. package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +53 -19
  72. package/lib/plugin/internal/source/SourcePluginWorkspace.js +111 -22
  73. package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
  74. package/lib/plugin/internal/source/buildSourcePlugin.d.ts +34 -18
  75. package/lib/plugin/internal/source/buildSourcePlugin.js +52 -24
  76. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  77. package/lib/plugin/internal/source/computeCacheKey.d.ts +25 -7
  78. package/lib/plugin/internal/source/computeCacheKey.js +85 -34
  79. package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
  80. package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
  81. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +75 -102
  82. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
  83. package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
  84. package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
  85. package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
  86. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
  87. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
  88. package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
  89. package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
  90. package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
  91. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
  92. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
  93. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  94. package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
  95. package/lib/plugin/internal/source/pluginSourceState.js +20 -8
  96. package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
  97. package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
  98. package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
  99. package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
  100. package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
  101. package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
  102. package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
  103. package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
  104. package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
  105. package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
  106. package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
  107. package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
  108. package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
  109. package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
  110. package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
  111. package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
  112. package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
  113. package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
  114. package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
  115. package/lib/plugin/resolveCapabilityPlugins.js +67 -15
  116. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
  117. package/package.json +8 -8
  118. package/src/compiler/internal/build/BuildExecution.ts +46 -13
  119. package/src/compiler/internal/startResidentTransform.ts +12 -3
  120. package/src/compiler/internal/transformProjectInMemory.ts +10 -2
  121. package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
  122. package/src/internal/nativeBinary.ts +3 -3
  123. package/src/internal/pluginSource.ts +1 -0
  124. package/src/internal/resolveNodeBinary.ts +12 -1
  125. package/src/internal/runtimeExecutableIdentity.ts +227 -141
  126. package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
  127. package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
  128. package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
  129. package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
  130. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
  131. package/src/plugin/internal/load/loadProjectPlugins.ts +421 -211
  132. package/src/plugin/internal/readCapabilityResolution.ts +17 -6
  133. package/src/plugin/internal/source/GoEnvironmentReading.ts +178 -0
  134. package/src/plugin/internal/source/NativeSourcePackages.ts +365 -48
  135. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +83 -5
  136. package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
  137. package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
  138. package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
  139. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
  140. package/src/plugin/internal/source/SourcePluginWorkspace.ts +148 -25
  141. package/src/plugin/internal/source/buildSourcePlugin.ts +104 -36
  142. package/src/plugin/internal/source/computeCacheKey.ts +129 -55
  143. package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +152 -105
  144. package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
  145. package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
  146. package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
  147. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
  148. package/src/plugin/internal/source/pluginSourceState.ts +28 -12
  149. package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
  150. package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
  151. package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
  152. package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
  153. package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
  154. package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
  155. package/src/plugin/resolveCapabilityPlugins.ts +87 -13
@@ -4,6 +4,7 @@ import { SHARE_ENV, Worker } from "node:worker_threads";
4
4
 
5
5
  import { SidecarEnvironment } from "../../../compiler/internal/sharedHost/SidecarEnvironment";
6
6
  import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
7
+ import { PluginContentIdentities } from "./PluginContentIdentities";
7
8
  import { pluginBuildEnvironment } from "./pluginBuildEnvironment";
8
9
 
9
10
  /**
@@ -74,25 +75,92 @@ export namespace PluginBuildEnvironmentReadings {
74
75
  : undefined;
75
76
  }
76
77
 
78
+ /**
79
+ * Qualify a batch in one synchronous observation transaction. Shared SDK
80
+ * paths and the complete variable identity are read once, even when several
81
+ * plugin directories use them. Missing readings remain explicitly missing.
82
+ *
83
+ * WARNING (#1712): this map belongs only to the following synchronous proof.
84
+ * Never retain it across an await, delivery, build boundary or publication. A
85
+ * process-permanent positive witness would miss changed Go tools and GOENV
86
+ * files (#1516); independently checking it in every helper instead multiplies
87
+ * a thousands-file GOROOT scan by modules and proof layers.
88
+ *
89
+ * @evidence contracts/common.md#principled-implementation One current variable snapshot and native path observation are compared against each reading's own expected witness; conflicting expected states cannot both pass.
90
+ * @evidence contracts/common.md#clear-and-simple-design One batch operation owns shared validation without changing synchronous reads or native worker publication boundaries.
91
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Undefined is retained for absent or stale authority; no positive verdict survives this explicit observation transaction.
92
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs state scope, missing semantics and the performance/correctness regressions prevented by the lifetime restriction.
93
+ * @evidence contracts/portability.md#os-neutral-implementation Existing native witness keys preserve filesystem, link and ambient-variable distinctions; no platform case assumption is added.
94
+ * @evidence contracts/performance.md#efficient-algorithms Full variables are indexed once; each distinct witnessed path is queried once while each reading compares its own witness entries. Work is proportional to variable bytes, total witness entries and unique native dependencies rather than repeated native SDK scans.
95
+ * @evidence contracts/performance.md#reuse-equivalent-work Only current native observations within this synchronous batch are shared; the returned digests do not authorize reuse after another asynchronous boundary.
96
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The native observation map is call-local and the digest map transfers to its immediate proof caller; no handle or historical registry is acquired.
97
+ */
98
+ export function cachedAll(
99
+ directories: Iterable<string>,
100
+ ): ReadonlyMap<string, string | undefined> {
101
+ let variables: string | undefined;
102
+ const current = new Map<string, string>();
103
+ const result = new Map<string, string | undefined>();
104
+ for (const directory of directories) {
105
+ if (result.has(directory)) continue;
106
+ const known = readings.get(
107
+ directory + (variables ??= key("", process.env)),
108
+ );
109
+ result.set(
110
+ directory,
111
+ known !== undefined &&
112
+ PluginBuildEnvironmentWitness.holds(known.witness, undefined, current)
113
+ ? known.environment
114
+ : undefined,
115
+ );
116
+ }
117
+ return result;
118
+ }
119
+
77
120
  /**
78
121
  * Read synchronously for clients whose API owns synchronous native work.
79
122
  * Explicit refresh preserves the original fresh-comparison semantics.
80
123
  *
124
+ * WARNING (#1725): the map above ends with the process, and every consumer
125
+ * that proves a plugin-source state starts a new one: a bundler worker, a
126
+ * capability host, the language server. Without the project's record store
127
+ * each of them walked and hashed the whole GOROOT (1.7 s for the bundled
128
+ * toolchain) on its first proof. A caller that names its project lets a miss
129
+ * prove the SDK and executables from the plugin cache's records.
130
+ *
131
+ * @param directory Plugin source directory whose build environment is read.
132
+ * @param refresh Bypass a cached reading.
133
+ * @param projectRoot Project whose plugin cache root holds the records.
81
134
  * @evidence contracts/common.md#principled-implementation The same native reader constructs a digest paired with its pre-read witness; optional cached reuse validates current variables/metadata. A fresh synchronous reading is published without a separate post-read qualification here and relies on the native reader's premises.
82
135
  * @evidence contracts/common.md#clear-and-simple-design The synchronous boundary shares cached authority and delegates fresh observation to its owning reader.
83
136
  * @evidence contracts/common.md#prohibited-implementation-shortcuts A requested refresh actually probes the selected native toolchain.
84
137
  * @evidence contracts/common.md#meaningful-documentation Native prose identifies the synchronous execution owner and explicit refresh meaning.
85
138
  * @evidence contracts/portability.md#os-neutral-implementation Native Go selection and filesystem witnesses remain delegated to the shared reader.
86
139
  * @evidence contracts/performance.md#efficient-algorithms Hits pay complete variable-text indexing and native witness queries. Misses/refresh merge environment values, perform actual toolchain/SDK/content work, then rebuild the full variable-text key for publication; native/path/file/name/value bytes contribute cost beyond invocation count.
87
- * @evidence contracts/performance.md#reuse-equivalent-work Synchronous and asynchronous clients share the same qualified readings.
140
+ * @evidence contracts/performance.md#reuse-equivalent-work Synchronous and asynchronous clients share the same qualified readings; a miss of a caller naming its project takes SDK and executable digests from the record store across processes.
88
141
  * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Retention belongs to the namespace; this call publishes one replacement record.
89
142
  */
90
- export function read(directory: string, refresh = false): string {
143
+ export function read(
144
+ directory: string,
145
+ refresh = false,
146
+ projectRoot?: string,
147
+ ): string {
91
148
  const known = refresh ? undefined : cached(directory);
92
149
  if (known !== undefined) return known;
93
150
  const env = SidecarEnvironment.merge(process.env);
94
151
  const witness: PluginBuildEnvironmentWitness.Record = new Map();
95
- const environment = pluginBuildEnvironment(directory, env, witness);
152
+ const environment = pluginBuildEnvironment(
153
+ directory,
154
+ env,
155
+ witness,
156
+ projectRoot === undefined
157
+ ? undefined
158
+ : PluginContentIdentities.open({
159
+ projectRoot,
160
+ env,
161
+ sources: [directory],
162
+ }),
163
+ );
96
164
  readings.set(key(directory, env), { environment, witness });
97
165
  return environment;
98
166
  }
@@ -106,6 +174,14 @@ export namespace PluginBuildEnvironmentReadings {
106
174
  * requests can create a worker after retirement. Refresh bypasses a cached
107
175
  * reading but can still share an equivalent in-flight request.
108
176
  *
177
+ * A caller that names its project lets the worker prove the SDK and
178
+ * executables from the plugin cache's records (#1722). The worker is its own
179
+ * isolate, so without them it reads the whole GOROOT once per process, which
180
+ * a bundler or runtime restarting on every edit pays every time.
181
+ *
182
+ * @param directory Plugin source directory whose build environment is read.
183
+ * @param refresh Bypass a cached reading.
184
+ * @param projectRoot Project whose plugin cache root holds the records.
109
185
  * @evidence contracts/common.md#principled-implementation Worker results publish only under their exact current variable identity and still-current native pre-read witness; changed or unwitnessable transfer windows reject instead of publishing authority or retrying indefinitely.
110
186
  * @evidence contracts/common.md#clear-and-simple-design Qualified hits return immediately; equivalent misses share one pending promise and exclusive queued worker request.
111
187
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Go/environment/SDK preparation occurs in the worker rather than blocking the async host or fabricating a digest from compiler output.
@@ -118,6 +194,7 @@ export namespace PluginBuildEnvironmentReadings {
118
194
  export async function prepare(
119
195
  directory: string,
120
196
  refresh = false,
197
+ projectRoot?: string,
121
198
  ): Promise<string> {
122
199
  const known = refresh ? undefined : cached(directory);
123
200
  if (known !== undefined) return known;
@@ -126,7 +203,7 @@ export namespace PluginBuildEnvironmentReadings {
126
203
  const existing = pending.get(identity);
127
204
  if (existing !== undefined) return existing;
128
205
  const request = (async () => {
129
- const reading = await observe(directory, env);
206
+ const reading = await observe(directory, env, projectRoot);
130
207
  if (
131
208
  identity !== key(directory, process.env) ||
132
209
  !PluginBuildEnvironmentWitness.holds(reading.witness)
@@ -158,6 +235,7 @@ export namespace PluginBuildEnvironmentReadings {
158
235
  function observe(
159
236
  directory: string,
160
237
  env: NodeJS.ProcessEnv,
238
+ projectRoot: string | undefined,
161
239
  ): Promise<Reading> {
162
240
  const request = queue.then(
163
241
  () =>
@@ -198,7 +276,7 @@ export namespace PluginBuildEnvironmentReadings {
198
276
  current.on("error", onError);
199
277
  current.on("exit", onExit);
200
278
  try {
201
- current.postMessage({ directory, env });
279
+ current.postMessage({ directory, env, projectRoot });
202
280
  } catch (error) {
203
281
  release(false);
204
282
  reject(error);
@@ -144,7 +144,9 @@ export namespace PluginBuildEnvironmentWitness {
144
144
  * observation. The existing private opt-in trace retains the first actual
145
145
  * mismatch without repeating its lookup or changing the refusal. An optional
146
146
  * observation label separates preparation and build epochs in that trace; it
147
- * does not change validation or authorize publication.
147
+ * does not change validation or authorize publication. A supplied native
148
+ * observation map belongs to one synchronous batch only; never reuse it after
149
+ * an await, delivery, environment mutation or publication boundary.
148
150
  *
149
151
  * @evidence contracts/common.md#principled-implementation Universal comparison requires every dependency to match its pre-read signature and immediately rejects a refused or changed path.
150
152
  * @evidence contracts/common.md#clear-and-simple-design Validation uses the same signature helper as capture, keeping identity and timestamp policy in one place.
@@ -153,16 +155,24 @@ export namespace PluginBuildEnvironmentWitness {
153
155
  * @evidence contracts/portability.md#os-neutral-implementation Validation reads actual Node metadata using the same link-following semantics as capture.
154
156
  * @evidence contracts/performance.md#efficient-algorithms Validation iterates dependencies until mismatch and performs native stat/link queries or ambient-variable serialization. Windows environment matching enumerates names and lowercases text per witnessed variable, so P alone does not bound that work. Native path/link/name/value/numeric bytes contribute cost; file content is not read and unavailable native observations share the helper's missing marker. An enabled private trace serializes the first mismatch's already-computed text through its existing bounded sink.
155
157
  *
156
- * @evidenceExclude contracts/performance.md#reuse-equivalent-work Each call must establish current validity; caching that answer would conceal external changes.
158
+ * @evidence contracts/performance.md#reuse-equivalent-work A caller-owned synchronous batch may share current native observations, comparing each witness against its own expected state; ordinary calls and all later observation phases establish new validity.
157
159
  * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Validation retains no handles or additional historical state.
158
160
  */
159
- export function holds(witness: Record, observation?: string): boolean {
161
+ export function holds(
162
+ witness: Record,
163
+ observation?: string,
164
+ currentReadings?: Map<string, string>,
165
+ ): boolean {
160
166
  for (const [file, recorded] of witness) {
161
- const current = file.startsWith(ENVIRONMENT_PREFIX)
162
- ? environmentSignature(file.slice(ENVIRONMENT_PREFIX.length))
163
- : file.startsWith(LINK_PREFIX)
164
- ? linkSignature(file.slice(LINK_PREFIX.length))
165
- : signature(file);
167
+ let current = currentReadings?.get(file);
168
+ if (current === undefined) {
169
+ current = file.startsWith(ENVIRONMENT_PREFIX)
170
+ ? environmentSignature(file.slice(ENVIRONMENT_PREFIX.length))
171
+ : file.startsWith(LINK_PREFIX)
172
+ ? linkSignature(file.slice(LINK_PREFIX.length))
173
+ : signature(file);
174
+ currentReadings?.set(file, current);
175
+ }
166
176
  if (current !== recorded) {
167
177
  E2ETrace.capabilityResolution(
168
178
  "plugin-build-environment-witness-mismatch",