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
@@ -0,0 +1,151 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import { E2ETrace } from "../../../internal/E2ETrace";
6
+ import type { PluginContentIdentities } from "./PluginContentIdentities";
7
+ import { SourceBuildCacheLayout } from "./SourceBuildCacheLayout";
8
+ import { recordCacheFileUse } from "./recordCacheFileUse";
9
+
10
+ /**
11
+ * Answers a plugin load computed from inputs it could identify by content, kept
12
+ * in the plugin cache root under that identity so a later process with equal
13
+ * inputs reads the answer instead of computing it again.
14
+ *
15
+ * WARNING (#1721, #1723): the key is the whole proof. A caller must fold into
16
+ * `identity` every input that can change the answer, as content identities
17
+ * (`PluginContentIdentities`) or the authoritative live probe results, never a
18
+ * path, a timestamp or a value it merely expects to be stable. An answer is
19
+ * written only after the computation succeeded on inputs the caller proved
20
+ * unchanged around it; a failed or unproved computation is never recorded,
21
+ * since a later process could not tell it from a current one.
22
+ *
23
+ * Entries are single files published by rename, aged out by the single-file
24
+ * collector and removed by `ttsc clean`. Their identity includes the format tag
25
+ * and the ttsc version, so another release never reads them.
26
+ *
27
+ * @evidence contracts/common.md#principled-implementation The content-addressed key carries every caller-declared input identity; reads accept only a matching format, version and kind, and writes are refused for unproved computations by the caller contract.
28
+ * @evidence contracts/common.md#clear-and-simple-design One owner stores and loads opaque answers; callers own key composition, validation of the answer shape and the decision to record.
29
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts No answer is guessed; a missing, corrupt or foreign entry is a miss and the caller computes.
30
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs state the key contract that prevents stale answers, the recording rule and the storage lifetime.
31
+ * @evidence contracts/portability.md#os-neutral-implementation Native path joins and same-directory rename publication; keys are hashed text, independent of path dialect.
32
+ * @evidenceExclude contracts/performance.md#efficient-algorithms Members state their own costs.
33
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work Members define reuse through the caller's identity.
34
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Members state retention; the namespace holds no state.
35
+ */
36
+ export namespace PluginLoadAnswers {
37
+ /**
38
+ * The answer recorded for `identity`, or `undefined` when none is.
39
+ *
40
+ * @param store The opened store, or `undefined` for no persistence.
41
+ * @param kind Answer family; families never share entries.
42
+ * @param identity Every input identity the answer depends on.
43
+ * @evidence contracts/common.md#principled-implementation An entry is returned only for the same format, version, kind and identity hash; the caller validates the value's shape.
44
+ * @evidence contracts/common.md#clear-and-simple-design One lookup returns an opaque value or undefined.
45
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Unreadable or foreign entries are misses.
46
+ * @evidence contracts/common.md#meaningful-documentation Parameters describe the identity contract.
47
+ * @evidence contracts/portability.md#os-neutral-implementation Native path join and file read.
48
+ * @evidence contracts/performance.md#efficient-algorithms Hashes the serialized identity and reads one small file; a hit refreshes its use time.
49
+ * @evidence contracts/performance.md#reuse-equivalent-work Processes share an answer only for an identical serialized identity under the same version.
50
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Nothing is retained in memory; the collector owns entry lifetime.
51
+ */
52
+ export function read(
53
+ store: PluginContentIdentities.Store | undefined,
54
+ kind: string,
55
+ identity: unknown,
56
+ ): unknown {
57
+ if (store === undefined) return undefined;
58
+ const file = answerFile(store, kind, identity);
59
+ try {
60
+ const entry = JSON.parse(
61
+ fs.readFileSync(file, "utf8"),
62
+ ) as Partial<IEntry>;
63
+ if (
64
+ entry.format !== FORMAT ||
65
+ entry.version !== store.version ||
66
+ entry.kind !== kind ||
67
+ !Object.prototype.hasOwnProperty.call(entry, "value")
68
+ )
69
+ return undefined;
70
+ recordCacheFileUse(file);
71
+ E2ETrace.capabilityResolution("plugin-load-answer", {
72
+ kind,
73
+ outcome: "reused",
74
+ });
75
+ return entry.value;
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Record `value` as the answer for `identity`. Failures are ignored: the
83
+ * answer just computed stands, and the next process computes it again.
84
+ *
85
+ * @param store The opened store, or `undefined` for no persistence.
86
+ * @param kind Answer family.
87
+ * @param identity Every input identity the answer depends on.
88
+ * @param value JSON-serializable answer the caller proved for `identity`.
89
+ * @evidence contracts/common.md#principled-implementation Publication happens only when the caller decided the computation was proven; the entry records the same identity framing that read checks.
90
+ * @evidence contracts/common.md#clear-and-simple-design One atomic publication through the shared metadata writer.
91
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts A failed write never fails the load or fabricates an entry.
92
+ * @evidence contracts/common.md#meaningful-documentation Native prose states failure behavior.
93
+ * @evidence contracts/portability.md#os-neutral-implementation Same-directory staging and rename through the shared writer.
94
+ * @evidence contracts/performance.md#efficient-algorithms Serializes the identity and value once and writes one small file.
95
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work Publication creates the reusable entry; read owns reuse.
96
+ * @evidence contracts/performance.md#bound-retention-and-release-resources One file per identity, removed by the single-file collector or `ttsc clean`; staging cleanup belongs to the shared writer.
97
+ */
98
+ export function write(
99
+ store: PluginContentIdentities.Store | undefined,
100
+ kind: string,
101
+ identity: unknown,
102
+ value: unknown,
103
+ ): void {
104
+ if (store === undefined) return;
105
+ const file = answerFile(store, kind, identity);
106
+ try {
107
+ fs.mkdirSync(path.dirname(file), { recursive: true });
108
+ SourceBuildCacheLayout.replaceCacheMetadataFile(
109
+ file,
110
+ JSON.stringify({
111
+ format: FORMAT,
112
+ version: store.version,
113
+ kind,
114
+ value,
115
+ } satisfies IEntry),
116
+ );
117
+ E2ETrace.capabilityResolution("plugin-load-answer", {
118
+ kind,
119
+ outcome: "recorded",
120
+ });
121
+ } catch {
122
+ // The answer just computed stands; only its reuse is lost.
123
+ }
124
+ }
125
+
126
+ /** Entry format tag; move it when the entry shape changes. */
127
+ const FORMAT = "ttsc-plugin-load-answer-v1";
128
+
129
+ interface IEntry {
130
+ format: string;
131
+ version: string;
132
+ kind: string;
133
+ value: unknown;
134
+ }
135
+
136
+ function answerFile(
137
+ store: PluginContentIdentities.Store,
138
+ kind: string,
139
+ identity: unknown,
140
+ ): string {
141
+ const key = crypto
142
+ .createHash("sha256")
143
+ .update(JSON.stringify([FORMAT, store.version, kind, identity]))
144
+ .digest("hex");
145
+ return path.join(
146
+ store.root,
147
+ SourceBuildCacheLayout.ANSWER_CACHE_DIRNAME,
148
+ `${key}.json`,
149
+ );
150
+ }
151
+ }
@@ -53,6 +53,18 @@ export namespace SourceBuildCacheLayout {
53
53
  /** Directory of the capability-resolution answers inside the cache root. */
54
54
  export const CAPABILITY_CACHE_DIRNAME = "capabilities";
55
55
 
56
+ /**
57
+ * Directory of the content identities a plugin load proves from metadata
58
+ * (`PluginContentIdentities`) inside the cache root.
59
+ */
60
+ export const IDENTITY_CACHE_DIRNAME = "identities";
61
+
62
+ /**
63
+ * Directory of the plugin-load answers keyed by those identities, such as Go
64
+ * package selections and runtime capabilities (`PluginLoadAnswers`).
65
+ */
66
+ export const ANSWER_CACHE_DIRNAME = "answers";
67
+
56
68
  /** Directory of the ttsx runtime inside the cache root. */
57
69
  export const RUNTIME_CACHE_DIRNAME = "ttsx";
58
70
 
@@ -72,6 +84,8 @@ export namespace SourceBuildCacheLayout {
72
84
  export const CACHE_FILE_DIRNAMES: readonly string[] = [
73
85
  DESCRIPTOR_CACHE_DIRNAME,
74
86
  CAPABILITY_CACHE_DIRNAME,
87
+ IDENTITY_CACHE_DIRNAME,
88
+ ANSWER_CACHE_DIRNAME,
75
89
  ORPHAN_CACHE_DIRNAME,
76
90
  ];
77
91
 
@@ -3,9 +3,12 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
5
  import { OwnedSynchronousProcess } from "../../../internal/OwnedSynchronousProcess";
6
+ import { GoEnvironmentReading } from "./GoEnvironmentReading";
6
7
  import { GoSourceInputs } from "./GoSourceInputs";
7
- import { copiesPluginSourceEntry } from "./copiesPluginSourceEntry";
8
+ import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
9
+ import type { PluginContentIdentities } from "./PluginContentIdentities";
8
10
  import { SourcePluginAdmission } from "./SourcePluginAdmission";
11
+ import { copiesPluginSourceEntry } from "./copiesPluginSourceEntry";
9
12
  import { formatGoWorkPath } from "./formatGoWorkPath";
10
13
  import { spawnGoTool } from "./spawnGoTool";
11
14
 
@@ -13,20 +16,23 @@ import { spawnGoTool } from "./spawnGoTool";
13
16
  * Share generated Go workspace policy between package selection and building.
14
17
  *
15
18
  * Package selection must use the builder's overlays, generated workspace and
16
- * selected tool rather than an ancestor go.work found in a source directory.
17
- * A load may share installed-overlay manifest records under its pinned tool
18
- * and effective environment; each unique scratch is read independently. The
19
- * cold build owns a fresh reader over its proven materialized inputs. Go
20
- * supplies the workspace's required Go and toolchain directives. These reads
21
- * preserve the existing installation/version and sequential-stability limits.
19
+ * selected tool rather than an ancestor go.work found in a source directory. A
20
+ * load shares early replacement watch, package and build manifest records under
21
+ * its pinned tool and effective environment; identical current bytes in
22
+ * independently proven scratch inputs share their Go parse. A moved native tool
23
+ * clears that reader's syntax memo, and a toolchain retry starts a new reader
24
+ * scope. Each caller has a diagnostic view over the shared native reader state;
25
+ * one plugin cannot inherit another plugin's failure label (#1720). Go supplies
26
+ * the workspace's required Go and toolchain directives. These reads preserve
27
+ * the existing installation/version and sequential-stability limits.
22
28
  *
23
29
  * @evidence contracts/common.md#principled-implementation Shared manifest acquisition, managed replacement admission, overlay filtering and Go-owned workspace version selection keep metadata and compilation under the same workspace policy; callers own source materialization and effective environment.
24
30
  * @evidence contracts/common.md#clear-and-simple-design One namespace owns the generated workspace and its module reader, overlay discovery and Go launch diagnostics; no second manifest parser or build-tag interpreter is introduced.
25
31
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Actual Go mod edit and work use commands interpret manifests; selected tool errors propagate and no static workspace version replaces Go's decision.
26
32
  * @evidence contracts/common.md#meaningful-documentation Native prose explains shared policy and caller ownership; private helpers remain part of this operation's review surface.
27
33
  * @evidence contracts/portability.md#os-neutral-implementation Native fs/path operations preserve path spelling and formatGoWorkPath owns Go syntax quoting; subprocesses use the existing native wrapper owner with hidden windows.
28
- * @evidence contracts/performance.md#efficient-algorithms Workspace construction scans module and replacement populations; each module reader memoizes absolute directory results within its owning load or materialized build. Overlay discovery recursively scans admitted directories and sorts paths; native Go output and JSON parsing scale with complete bytes.
29
- * @evidence contracts/performance.md#reuse-equivalent-work A metadata load shares equivalent installed-overlay observations under one pinned tool/effective environment; unique scratch paths are independent. A materialized build uses its own reader, and no memo crosses a load or changed environment. Metadata and build share policy rather than pretending their independently materialized sources are identical.
34
+ * @evidence contracts/performance.md#efficient-algorithms Workspace construction scans module and replacement populations; each reader reads current manifest bytes and memoizes equivalent Go syntax within its pinned native tool/load. Overlay discovery recursively scans admitted directories and sorts paths; native Go output and JSON parsing scale with complete bytes.
35
+ * @evidence contracts/performance.md#reuse-equivalent-work Metadata and materialized builds share Go parsing only for identical current manifest bytes under the same pinned tool/effective environment. Independent materialization/content proof remains with each caller; tool movement clears syntax reuse and retries start another scope.
30
36
  * @evidence contracts/performance.md#bound-retention-and-release-resources Readers retain module records only for their enclosing load or materialized build; command capture cleanup belongs to spawnGoTool and scratch lifetime belongs to the caller. Native commands can mutate Go-owned state and their selected-child completion does not prove arbitrary descendants retired.
31
37
  */
32
38
  export namespace SourcePluginWorkspace {
@@ -55,7 +61,8 @@ export namespace SourcePluginWorkspace {
55
61
  }
56
62
 
57
63
  /**
58
- * Write the native builder's generated workspace and let Go select its version.
64
+ * Write the native builder's generated workspace and let Go select its
65
+ * version.
59
66
  *
60
67
  * @evidence contracts/common.md#principled-implementation Actual manifest records drive managed overlay filtering and replacement admission; Go work use supplies required directives.
61
68
  * @evidence contracts/common.md#clear-and-simple-design The writer shares one reader across source/overlay queries and delegates manifest interpretation to Go.
@@ -173,6 +180,7 @@ export namespace SourcePluginWorkspace {
173
180
  interface GoModInfo {
174
181
  readonly modulePath: string | null;
175
182
  readonly replacements: readonly GoModReplacement[];
183
+ readonly directives: NonNullable<GoModJson["Replace"]>;
176
184
  }
177
185
 
178
186
  /**
@@ -190,17 +198,19 @@ export namespace SourcePluginWorkspace {
190
198
  export interface GoModReader {
191
199
  /**
192
200
  * Observe the module at this native directory within the reader context.
201
+ * The optional diagnostic owner overrides the creating caller's label only;
202
+ * it changes no selected-tool, environment or byte-content authority.
193
203
  *
194
204
  * @evidence contracts/common.md#principled-implementation The signature associates a native directory with the selected reader's Go-owned module and replacement observation.
195
- * @evidence contracts/common.md#clear-and-simple-design One input and one record expose the manifest policy needed by the workspace writer.
205
+ * @evidence contracts/common.md#clear-and-simple-design A directory and optional diagnostic label expose manifest policy without treating the label as native cache authority.
196
206
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The signature supplies no parser, alternate selection policy or stability certificate.
197
- * @evidence contracts/common.md#meaningful-documentation Native prose states that the result belongs to the reader context rather than a context-independent directory cache.
207
+ * @evidence contracts/common.md#meaningful-documentation Native prose states selected-context result ownership and the optional caller label, which changes failure attribution only.
198
208
  * @evidence contracts/portability.md#os-neutral-implementation The directory is a native filesystem path; its resolution belongs to the implementing reader.
199
209
  * @evidence contracts/performance.md#efficient-algorithms A method signature executes no algorithm; createGoModReader owns acquisition and parsing.
200
210
  * @evidence contracts/performance.md#reuse-equivalent-work Implementations supply observations within their selected tool/environment scope; this signature establishes no cross-context memo.
201
211
  * @evidence contracts/performance.md#bound-retention-and-release-resources The signature acquires no resource; record retention belongs to the reader's enclosing load or materialized build.
202
212
  */
203
- read(dir: string): GoModInfo;
213
+ read(dir: string, diagnosticPlugin?: string): GoModInfo;
204
214
  }
205
215
 
206
216
  interface GoModJson {
@@ -224,36 +234,148 @@ export namespace SourcePluginWorkspace {
224
234
  }
225
235
 
226
236
  /**
227
- * Read each module once through Go within one selected-tool context.
237
+ * Read current manifest bytes and share identical Go parses within one pinned
238
+ * selected-tool/load context, including copied scratch modules. An optional
239
+ * registry shares native reader state from the first watch query onward; its
240
+ * owner must keep one fixed effective environment and discard it on retry.
241
+ * Caller views share that native state and syntax, while each default
242
+ * diagnostic label remains with its current caller. An optional record store
243
+ * only carries the Go environment-file discovery hint across processes; it
244
+ * never carries a parse.
228
245
  *
229
- * @evidence contracts/common.md#principled-implementation Actual go mod edit JSON determines module/replacement identities; missing go.mod returns the existing empty module observation.
230
- * @evidence contracts/common.md#clear-and-simple-design An absolute-directory map wraps one manifest acquisition helper.
246
+ * @evidence contracts/common.md#principled-implementation Actual go mod edit JSON determines module/replacement identities; current bytes, selected/dispatched-tool metadata and Go's environment-file witness qualify reuse under the existing metadata-distinguishability premise. Environment values are copied at reader creation. Missing go.mod returns the existing empty module observation, and moving tool/manifest inputs cannot publish a parsed record.
247
+ * @evidence contracts/common.md#clear-and-simple-design One factory acquires load-owned native reader state by selected tool; a thin caller view forwards its diagnostic label without duplicating the byte-content map. Current reads and post-parse byte/tool comparisons qualify insertion.
231
248
  * @evidence contracts/common.md#prohibited-implementation-shortcuts No custom Go manifest parser or global context-insensitive memo is introduced.
232
249
  * @evidence contracts/common.md#meaningful-documentation Native prose states this operation's authority and delegated boundaries; fields retain separate native comments.
233
250
  * @evidence contracts/portability.md#os-neutral-implementation Native path resolution and temporary cwd avoid deep Windows working-directory limits; the selected tool handles manifest syntax.
234
- * @evidence contracts/performance.md#efficient-algorithms Each distinct directory runs one existence query and at most one Go mod edit plus complete JSON parsing.
235
- * @evidence contracts/performance.md#reuse-equivalent-work The caller may share this reader only within one load and the same pinned tool/effective environment; directory results are not cross-load authority.
236
- * @evidence contracts/performance.md#bound-retention-and-release-resources The map grows with distinct observed directories for its caller's lifetime; process/output capture cleanup remains delegated.
251
+ * @evidence contracts/performance.md#efficient-algorithms Reader acquisition performs a selected-tool registry lookup; every nonmissing read qualifies tool/environment metadata and reads current manifest bytes. A cold or stale witness first observes the selected launcher; an unavailable or nonfile launcher skips optional environment acquisition and permits only the actual uncached parse attempt. An observed file lends its same pre-read stat to the witness before acquiring current Go values and GOENV evidence through the common reader, without a GOROOT walk. Only complete native GOENV/GOROOT locations qualify syntax sharing; a dispatched tool is witnessed before parsing. Unavailable native environment acquisition disables syntax sharing while preserving actual parse success or failure. Each distinct byte string that earns stable successful syntax in that tool epoch runs Go mod edit and complete JSON parsing once, then rechecks bytes/tool metadata before caching; copied manifests share that parse. Failed queries remain uncached and report the current caller's diagnostic label.
252
+ * @evidence contracts/performance.md#reuse-equivalent-work The caller may share this reader only within one load and the same pinned tool/effective environment; parsed syntax is not cross-load or native containment authority. Caller views share native state while preserving failure attribution; diagnostic labels are not syntax cache inputs.
253
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The map retains distinct manifest bytes and parsed syntax for its caller's load/build lifetime; process/output capture cleanup remains delegated.
237
254
  */
238
255
  export function createGoModReader(
239
256
  goBinary: string,
240
257
  pluginName: string,
241
258
  env: NodeJS.ProcessEnv,
259
+ readers?: Map<string, GoModReader>,
260
+ identities?: PluginContentIdentities.Store,
242
261
  ): GoModReader {
262
+ const existing = readers?.get(goBinary);
263
+ if (existing !== undefined)
264
+ return {
265
+ read: (dir, diagnosticPlugin = pluginName) =>
266
+ existing.read(dir, diagnosticPlugin),
267
+ };
268
+ // WARNING (#1712): parse identical bytes once in this pinned tool/load
269
+ // context, including early replacement watch projection and copied scratch
270
+ // manifests. The optional registry belongs to one fixed effective environment
271
+ // and one load; it is never shared with a toolchain retry. Always read current bytes;
272
+ // a pathname or metadata-only memo misses manifest edits. Never promote
273
+ // this map to a process cache: tool/environment epochs change Go authority.
274
+ // Fixed variable strings do not freeze GOENV or a dispatched Go executable.
275
+ // Requalify their actual native evidence before every syntax hit; missing
276
+ // environment authority permits a fresh parse only, never positive reuse.
277
+ // WARNING (#1720): diagnostic names are per caller, not a syntax/cache input.
278
+ // Shared views must forward their current label to every failed native read.
243
279
  const cache = new Map<string, GoModInfo>();
244
- return {
245
- read(dir) {
280
+ const environment = { ...env };
281
+ let toolWitness: PluginBuildEnvironmentWitness.Record | undefined;
282
+ const reader: GoModReader = {
283
+ read(dir, diagnosticPlugin = pluginName) {
246
284
  OwnedSynchronousProcess.checkpoint();
247
285
  const resolved = path.resolve(dir);
248
- const cached = cache.get(resolved);
286
+ const manifest = path.join(resolved, "go.mod");
287
+ let bytes: Buffer;
288
+ try {
289
+ bytes = fs.readFileSync(manifest);
290
+ } catch (error) {
291
+ if ((error as NodeJS.ErrnoException).code === "ENOENT")
292
+ return emptyGoModInfo();
293
+ throw error;
294
+ }
295
+ if (
296
+ toolWitness === undefined ||
297
+ !PluginBuildEnvironmentWitness.holds(toolWitness)
298
+ ) {
299
+ cache.clear();
300
+ const witness: PluginBuildEnvironmentWitness.Record = new Map();
301
+ let launcher: fs.BigIntStats | undefined;
302
+ try {
303
+ launcher = fs.statSync(goBinary, { bigint: true });
304
+ } catch {
305
+ // Unavailable metadata cannot qualify optional syntax reuse.
306
+ }
307
+ if (launcher?.isFile() === true)
308
+ PluginBuildEnvironmentWitness.add(witness, goBinary, launcher);
309
+ // GOENV can change toolchain selection without changing env strings.
310
+ // Share its existing native discovery owner; do not scan GOROOT here.
311
+ // Do not launch a missing/unobservable tool twice just to learn that
312
+ // optional cache qualification is unavailable (#1712). The actual
313
+ // mod-edit attempt below still owns its current failure or success.
314
+ const observed =
315
+ launcher?.isFile() === true
316
+ ? GoEnvironmentReading.read(
317
+ goBinary,
318
+ os.tmpdir(),
319
+ environment,
320
+ ["GOROOT"],
321
+ witness,
322
+ identities,
323
+ )
324
+ : undefined;
325
+ if (
326
+ typeof observed?.GOROOT === "string" &&
327
+ path.isAbsolute(observed.GOROOT)
328
+ )
329
+ PluginBuildEnvironmentWitness.add(
330
+ witness,
331
+ path.join(
332
+ observed.GOROOT,
333
+ "bin",
334
+ process.platform === "win32" ? "go.exe" : "go",
335
+ ),
336
+ );
337
+ const complete =
338
+ typeof observed?.GOROOT === "string" &&
339
+ path.isAbsolute(observed.GOROOT) &&
340
+ typeof observed.GOENV === "string" &&
341
+ (observed.GOENV === "off" || path.isAbsolute(observed.GOENV));
342
+ toolWitness =
343
+ complete && PluginBuildEnvironmentWitness.holds(witness)
344
+ ? witness
345
+ : undefined;
346
+ }
347
+ // UTF-8 decoding is lossy for malformed input. Only identical bytes
348
+ // can share Go syntax, even when Go serializes two inputs identically.
349
+ const byteKey = bytes.toString("base64");
350
+ const cached = cache.get(byteKey);
249
351
  if (cached !== undefined) {
250
352
  return cached;
251
353
  }
252
- const info = readGoModInfo(resolved, goBinary, pluginName, env);
253
- cache.set(resolved, info);
354
+ const info = readGoModInfo(
355
+ resolved,
356
+ goBinary,
357
+ diagnosticPlugin,
358
+ environment,
359
+ );
360
+ if (!fs.readFileSync(manifest).equals(bytes))
361
+ throw new Error(
362
+ `ttsc: go.mod changed while Go was reading ${manifest}`,
363
+ );
364
+ if (
365
+ toolWitness !== undefined &&
366
+ !PluginBuildEnvironmentWitness.holds(toolWitness)
367
+ )
368
+ throw new Error(
369
+ `ttsc: Go tool or environment changed while reading ${manifest}`,
370
+ );
371
+ // An unavailable environment observation can still execute a fresh Go
372
+ // parse, but it cannot authorize sharing that result on another call.
373
+ if (toolWitness !== undefined) cache.set(byteKey, info);
254
374
  return info;
255
375
  },
256
376
  };
377
+ readers?.set(goBinary, reader);
378
+ return reader;
257
379
  }
258
380
 
259
381
  function readGoModInfo(
@@ -313,6 +435,7 @@ export namespace SourcePluginWorkspace {
313
435
 
314
436
  return {
315
437
  modulePath: json.Module?.Path ?? null,
438
+ directives: json.Replace ?? [],
316
439
  replacements: (json.Replace ?? [])
317
440
  .map(jsonReplacementToGoModReplacement)
318
441
  .filter((replacement) => replacement !== null),
@@ -322,6 +445,7 @@ export namespace SourcePluginWorkspace {
322
445
  function emptyGoModInfo(): GoModInfo {
323
446
  return {
324
447
  modulePath: null,
448
+ directives: [],
325
449
  replacements: [],
326
450
  };
327
451
  }
@@ -441,6 +565,5 @@ export namespace SourcePluginWorkspace {
441
565
  }
442
566
  }
443
567
 
444
-
445
568
  const TTSC_GO_MODULE_PATH = "github.com/samchon/ttsc/packages/ttsc";
446
569
  }