ttsc 0.31.0 → 0.31.1
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.
- package/cmd/platform/main.go +2 -2
- package/internal/sourceprocess/contain_windows.go +10 -13
- package/internal/sourceprocess/started_windows.go +41 -0
- package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
- package/internal/watchhelper/run.go +30 -20
- package/internal/watchhelper/run_other.go +4 -4
- package/internal/watchhelper/run_windows.go +471 -0
- package/internal/watchhelper/watchhelper.go +8 -2
- package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
- package/lib/internal/nativeBinary.d.ts +3 -3
- package/lib/internal/nativeBinary.js +3 -3
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -13
- package/lib/plugin/internal/load/loadProjectPlugins.js +61 -36
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +42 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js +110 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +7 -7
- package/lib/plugin/internal/source/NativeSourcePackages.js +33 -20
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +36 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
- package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +50 -19
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +109 -22
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +19 -16
- package/lib/plugin/internal/source/buildSourcePlugin.js +27 -22
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +4 -2
- package/lib/plugin/internal/source/computeCacheKey.js +15 -6
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +2 -68
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
- package/package.json +8 -8
- package/src/internal/nativeBinary.ts +3 -3
- package/src/plugin/internal/load/loadProjectPlugins.ts +209 -117
- package/src/plugin/internal/source/GoEnvironmentReading.ts +109 -0
- package/src/plugin/internal/source/NativeSourcePackages.ts +103 -43
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +42 -0
- package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +143 -25
- package/src/plugin/internal/source/buildSourcePlugin.ts +62 -34
- package/src/plugin/internal/source/computeCacheKey.ts +26 -8
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +8 -76
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
|
@@ -17,8 +17,8 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
17
17
|
/**
|
|
18
18
|
* Observe Go-selected packages under the native builder's workspace policy.
|
|
19
19
|
*
|
|
20
|
-
* Metadata does not compile dependencies or certify a later build. A cold
|
|
21
|
-
*
|
|
20
|
+
* Metadata does not compile dependencies or certify a later build. A cold build
|
|
21
|
+
* repeats ownership admission in its proven, materialized workspace.
|
|
22
22
|
*
|
|
23
23
|
* @evidence contracts/common.md#principled-implementation The selected Go tool, effective environment, shared workspace writer and actual copied package inputs determine package names and errors; Go remains the build-constraint and package-validity authority.
|
|
24
24
|
* @evidence contracts/common.md#clear-and-simple-design One namespace separates contextual observation, metadata framing and ownership admission; source-only contributions are observed inside their selected host module.
|
|
@@ -99,7 +99,10 @@ export namespace NativeSourcePackages {
|
|
|
99
99
|
if (opts.proposal === true) {
|
|
100
100
|
// Only root manifests change. Relative Go inputs retain the source
|
|
101
101
|
// module layout, while its irrelevant graph/toolchain stays inactive.
|
|
102
|
-
const hostModule = resolvePluginGoModule(
|
|
102
|
+
const hostModule = resolvePluginGoModule(
|
|
103
|
+
genericHostSource(),
|
|
104
|
+
opts.pluginName,
|
|
105
|
+
).moduleRoot;
|
|
103
106
|
for (const name of ["go.mod", "go.sum"]) {
|
|
104
107
|
const manifest = path.join(hostModule, name);
|
|
105
108
|
const target = path.join(scratch, name);
|
|
@@ -110,19 +113,28 @@ export namespace NativeSourcePackages {
|
|
|
110
113
|
for (const input of opts.packages) {
|
|
111
114
|
if (input.source === undefined) continue;
|
|
112
115
|
const contributor = { source: input.source, name: input.name! };
|
|
113
|
-
SourcePluginAdmission.requireContributorPackage(
|
|
116
|
+
SourcePluginAdmission.requireContributorPackage(
|
|
117
|
+
opts.pluginName,
|
|
118
|
+
contributor,
|
|
119
|
+
);
|
|
114
120
|
const target = path.resolve(scratch, input.entry);
|
|
115
|
-
if (
|
|
121
|
+
if (
|
|
122
|
+
path.relative(scratch, target).startsWith("..") ||
|
|
123
|
+
target === scratch
|
|
124
|
+
)
|
|
116
125
|
throw new Error("ttsc: invalid package selection target");
|
|
117
126
|
if (fs.existsSync(target))
|
|
118
|
-
throw new Error(
|
|
127
|
+
throw new Error(
|
|
128
|
+
`ttsc: package selection target already exists: ${target}`,
|
|
129
|
+
);
|
|
119
130
|
SourcePluginWorkspace.materialize(input.source, target);
|
|
120
131
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
const reader = SourcePluginWorkspace.createGoModReader(
|
|
133
|
+
goBinary,
|
|
134
|
+
opts.pluginName,
|
|
135
|
+
env,
|
|
136
|
+
opts.readers,
|
|
137
|
+
);
|
|
126
138
|
SourcePluginWorkspace.writeGoWork(
|
|
127
139
|
scratch,
|
|
128
140
|
SourcePluginWorkspace.findTtscOverlayDirs(),
|
|
@@ -142,9 +154,13 @@ export namespace NativeSourcePackages {
|
|
|
142
154
|
taskFailure = { error };
|
|
143
155
|
throw error;
|
|
144
156
|
} finally {
|
|
145
|
-
SourceNativeRetirement.releaseResource(
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
SourceNativeRetirement.releaseResource(
|
|
158
|
+
scratch,
|
|
159
|
+
() => {
|
|
160
|
+
fs.rmSync(scratch, { recursive: true, force: true });
|
|
161
|
+
},
|
|
162
|
+
taskFailure,
|
|
163
|
+
);
|
|
148
164
|
}
|
|
149
165
|
}
|
|
150
166
|
|
|
@@ -182,7 +198,9 @@ export namespace NativeSourcePackages {
|
|
|
182
198
|
readers,
|
|
183
199
|
packages: group.map(({ entry }) => ({ entry })),
|
|
184
200
|
});
|
|
185
|
-
group.forEach(({ index }, offset) => {
|
|
201
|
+
group.forEach(({ index }, offset) => {
|
|
202
|
+
output[index] = observations[offset]!;
|
|
203
|
+
});
|
|
186
204
|
}
|
|
187
205
|
return output;
|
|
188
206
|
}
|
|
@@ -190,11 +208,11 @@ export namespace NativeSourcePackages {
|
|
|
190
208
|
/**
|
|
191
209
|
* Propose ownership with the candidate-selected tool before final admission.
|
|
192
210
|
*
|
|
193
|
-
* Nested transform sources retain their admitted source-module layout but
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
211
|
+
* Nested transform sources retain their admitted source-module layout but use
|
|
212
|
+
* unchanged generic host root manifests in scratch. Their outer module graph
|
|
213
|
+
* and toolchain are not source-only build authority. Module-root and check
|
|
214
|
+
* sources retain their actual owning module. Executable proposals must agree
|
|
215
|
+
* in the actual own module; linked proposals in each chosen actual host.
|
|
198
216
|
*
|
|
199
217
|
* @evidence contracts/common.md#principled-implementation Candidate tool selection occurs at the original owning module and effective environment; only prospective nested source-only manifests change in owned scratch before Go interprets the normal generated workspace.
|
|
200
218
|
* @evidence contracts/common.md#clear-and-simple-design One grouping pass shares each module/proposal context and returns whether actual own-module observation already occurred.
|
|
@@ -210,7 +228,10 @@ export namespace NativeSourcePackages {
|
|
|
210
228
|
env: NodeJS.ProcessEnv,
|
|
211
229
|
readers = new Map<string, SourcePluginWorkspace.GoModReader>(),
|
|
212
230
|
): Array<{ observation: Package; ownModule: boolean }> {
|
|
213
|
-
const groups = new Map<
|
|
231
|
+
const groups = new Map<
|
|
232
|
+
string,
|
|
233
|
+
Array<{ index: number; entry: string; ownModule: boolean }>
|
|
234
|
+
>();
|
|
214
235
|
inputs.forEach((input, index) => {
|
|
215
236
|
const target = resolvePluginGoModule(input.source, input.label);
|
|
216
237
|
const ownModule = input.ownModule === true || target.entry === ".";
|
|
@@ -219,11 +240,15 @@ export namespace NativeSourcePackages {
|
|
|
219
240
|
group.push({ index, entry: target.entry, ownModule });
|
|
220
241
|
groups.set(key, group);
|
|
221
242
|
});
|
|
222
|
-
const output: Array<{ observation: Package; ownModule: boolean }> =
|
|
243
|
+
const output: Array<{ observation: Package; ownModule: boolean }> =
|
|
244
|
+
new Array(inputs.length);
|
|
223
245
|
for (const group of groups.values()) {
|
|
224
246
|
const first = inputs[group[0]!.index]!;
|
|
225
247
|
const observations = inspect({
|
|
226
|
-
source: first.source,
|
|
248
|
+
source: first.source,
|
|
249
|
+
pluginName: first.label,
|
|
250
|
+
env,
|
|
251
|
+
readers,
|
|
227
252
|
proposal: !group[0]!.ownModule,
|
|
228
253
|
packages: group.map(({ entry }) => ({ entry })),
|
|
229
254
|
});
|
|
@@ -258,21 +283,38 @@ export namespace NativeSourcePackages {
|
|
|
258
283
|
OwnedSynchronousProcess.checkpoint();
|
|
259
284
|
const result = spawnGoTool(
|
|
260
285
|
opts.goBinary,
|
|
261
|
-
[
|
|
286
|
+
[
|
|
287
|
+
"list",
|
|
288
|
+
...GoSourceInputs.BUILD_FLAGS,
|
|
289
|
+
"-find",
|
|
290
|
+
"-e",
|
|
291
|
+
"-json=Dir,Name,GoFiles,CgoFiles,Error",
|
|
292
|
+
...entries,
|
|
293
|
+
],
|
|
262
294
|
{ cwd: opts.cwd, encoding: "utf8", env: opts.env, windowsHide: true },
|
|
263
295
|
);
|
|
264
296
|
OwnedSynchronousProcess.checkpoint();
|
|
265
297
|
if (result.error !== undefined)
|
|
266
|
-
throw new Error(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
298
|
+
throw new Error(
|
|
299
|
+
SourcePluginWorkspace.goSpawnFailureMessage(
|
|
300
|
+
`selecting Go packages for plugin "${opts.pluginName}"`,
|
|
301
|
+
opts.pluginName,
|
|
302
|
+
opts.goBinary,
|
|
303
|
+
opts.cwd,
|
|
304
|
+
result.error,
|
|
305
|
+
),
|
|
306
|
+
);
|
|
270
307
|
if (result.status !== 0)
|
|
271
|
-
throw new Error(
|
|
308
|
+
throw new Error(
|
|
309
|
+
`ttsc: selecting Go packages for plugin "${opts.pluginName}" failed:\n${result.stderr || result.stdout}`,
|
|
310
|
+
);
|
|
272
311
|
const packages = parse(result.stdout);
|
|
273
|
-
const selected = new Map(
|
|
274
|
-
|
|
275
|
-
|
|
312
|
+
const selected = new Map(
|
|
313
|
+
packages.map((input) => [
|
|
314
|
+
input.Dir === undefined ? undefined : fs.realpathSync.native(input.Dir),
|
|
315
|
+
input,
|
|
316
|
+
]),
|
|
317
|
+
);
|
|
276
318
|
return opts.entries.map((entry) => {
|
|
277
319
|
const dir = fs.realpathSync.native(path.resolve(opts.cwd, entry));
|
|
278
320
|
const input = selected.get(dir);
|
|
@@ -300,12 +342,21 @@ export namespace NativeSourcePackages {
|
|
|
300
342
|
expected?: "executable" | "linked",
|
|
301
343
|
): "executable" | "linked" {
|
|
302
344
|
if (input.Error !== undefined)
|
|
303
|
-
throw new Error(
|
|
304
|
-
|
|
305
|
-
|
|
345
|
+
throw new Error(
|
|
346
|
+
`ttsc: plugin "${label}" Go package selection failed: ${input.Error.Err}`,
|
|
347
|
+
);
|
|
348
|
+
if (
|
|
349
|
+
input.Name === undefined ||
|
|
350
|
+
(input.GoFiles?.length ?? 0) + (input.CgoFiles?.length ?? 0) === 0
|
|
351
|
+
)
|
|
352
|
+
throw new Error(
|
|
353
|
+
`ttsc: plugin "${label}" source has no Go-selected production files: ${input.Dir ?? "unknown package"}`,
|
|
354
|
+
);
|
|
306
355
|
const actual = input.Name === "main" ? "executable" : "linked";
|
|
307
356
|
if (expected !== undefined && actual !== expected)
|
|
308
|
-
throw new Error(
|
|
357
|
+
throw new Error(
|
|
358
|
+
`ttsc: plugin "${label}" Go package ownership disagrees with its proposal: selected ${actual}, expected ${expected} (${input.Dir ?? "unknown package"})`,
|
|
359
|
+
);
|
|
309
360
|
return actual;
|
|
310
361
|
}
|
|
311
362
|
|
|
@@ -331,7 +382,8 @@ export namespace NativeSourcePackages {
|
|
|
331
382
|
const token = output[offset]!;
|
|
332
383
|
if (start < 0) {
|
|
333
384
|
if (/\s/.test(token)) continue;
|
|
334
|
-
if (token !== "{")
|
|
385
|
+
if (token !== "{")
|
|
386
|
+
throw new Error("ttsc: invalid Go package metadata stream");
|
|
335
387
|
start = offset;
|
|
336
388
|
depth = 1;
|
|
337
389
|
continue;
|
|
@@ -347,17 +399,25 @@ export namespace NativeSourcePackages {
|
|
|
347
399
|
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
348
400
|
throw new Error("ttsc: invalid Go package metadata object");
|
|
349
401
|
const record = input as Package;
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
(
|
|
402
|
+
if (
|
|
403
|
+
(record.Dir !== undefined && typeof record.Dir !== "string") ||
|
|
404
|
+
(record.Name !== undefined && typeof record.Name !== "string") ||
|
|
405
|
+
[record.GoFiles, record.CgoFiles].some(
|
|
406
|
+
(files) =>
|
|
407
|
+
files !== undefined &&
|
|
408
|
+
(!Array.isArray(files) ||
|
|
409
|
+
files.some((file) => typeof file !== "string")),
|
|
410
|
+
) ||
|
|
411
|
+
(record.Error !== undefined &&
|
|
412
|
+
(record.Error === null || typeof record.Error.Err !== "string"))
|
|
413
|
+
)
|
|
355
414
|
throw new Error("ttsc: invalid Go package metadata fields");
|
|
356
415
|
packages.push(record);
|
|
357
416
|
start = -1;
|
|
358
417
|
}
|
|
359
418
|
}
|
|
360
|
-
if (start >= 0)
|
|
419
|
+
if (start >= 0)
|
|
420
|
+
throw new Error("ttsc: incomplete Go package metadata stream");
|
|
361
421
|
return packages;
|
|
362
422
|
}
|
|
363
423
|
}
|
|
@@ -74,6 +74,48 @@ export namespace PluginBuildEnvironmentReadings {
|
|
|
74
74
|
: undefined;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Qualify a batch in one synchronous observation transaction. Shared SDK
|
|
79
|
+
* paths and the complete variable identity are read once, even when several
|
|
80
|
+
* plugin directories use them. Missing readings remain explicitly missing.
|
|
81
|
+
*
|
|
82
|
+
* WARNING (#1712): this map belongs only to the following synchronous proof.
|
|
83
|
+
* Never retain it across an await, delivery, build boundary or publication. A
|
|
84
|
+
* process-permanent positive witness would miss changed Go tools and GOENV
|
|
85
|
+
* files (#1516); independently checking it in every helper instead multiplies
|
|
86
|
+
* a thousands-file GOROOT scan by modules and proof layers.
|
|
87
|
+
*
|
|
88
|
+
* @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.
|
|
89
|
+
* @evidence contracts/common.md#clear-and-simple-design One batch operation owns shared validation without changing synchronous reads or native worker publication boundaries.
|
|
90
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Undefined is retained for absent or stale authority; no positive verdict survives this explicit observation transaction.
|
|
91
|
+
* @evidence contracts/common.md#meaningful-documentation Native paragraphs state scope, missing semantics and the performance/correctness regressions prevented by the lifetime restriction.
|
|
92
|
+
* @evidence contracts/portability.md#os-neutral-implementation Existing native witness keys preserve filesystem, link and ambient-variable distinctions; no platform case assumption is added.
|
|
93
|
+
* @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.
|
|
94
|
+
* @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.
|
|
95
|
+
* @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.
|
|
96
|
+
*/
|
|
97
|
+
export function cachedAll(
|
|
98
|
+
directories: Iterable<string>,
|
|
99
|
+
): ReadonlyMap<string, string | undefined> {
|
|
100
|
+
let variables: string | undefined;
|
|
101
|
+
const current = new Map<string, string>();
|
|
102
|
+
const result = new Map<string, string | undefined>();
|
|
103
|
+
for (const directory of directories) {
|
|
104
|
+
if (result.has(directory)) continue;
|
|
105
|
+
const known = readings.get(
|
|
106
|
+
directory + (variables ??= key("", process.env)),
|
|
107
|
+
);
|
|
108
|
+
result.set(
|
|
109
|
+
directory,
|
|
110
|
+
known !== undefined &&
|
|
111
|
+
PluginBuildEnvironmentWitness.holds(known.witness, undefined, current)
|
|
112
|
+
? known.environment
|
|
113
|
+
: undefined,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
77
119
|
/**
|
|
78
120
|
* Read synchronously for clients whose API owns synchronous native work.
|
|
79
121
|
* Explicit refresh preserves the original fresh-comparison semantics.
|
|
@@ -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
|
-
* @
|
|
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(
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
?
|
|
165
|
-
:
|
|
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",
|
|
@@ -3,9 +3,11 @@ 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 {
|
|
8
|
+
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
8
9
|
import { SourcePluginAdmission } from "./SourcePluginAdmission";
|
|
10
|
+
import { copiesPluginSourceEntry } from "./copiesPluginSourceEntry";
|
|
9
11
|
import { formatGoWorkPath } from "./formatGoWorkPath";
|
|
10
12
|
import { spawnGoTool } from "./spawnGoTool";
|
|
11
13
|
|
|
@@ -13,20 +15,23 @@ import { spawnGoTool } from "./spawnGoTool";
|
|
|
13
15
|
* Share generated Go workspace policy between package selection and building.
|
|
14
16
|
*
|
|
15
17
|
* 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
|
-
*
|
|
18
|
-
* and effective environment;
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* selected tool rather than an ancestor go.work found in a source directory. A
|
|
19
|
+
* load shares early replacement watch, package and build manifest records under
|
|
20
|
+
* its pinned tool and effective environment; identical current bytes in
|
|
21
|
+
* independently proven scratch inputs share their Go parse. A moved native tool
|
|
22
|
+
* clears that reader's syntax memo, and a toolchain retry starts a new reader
|
|
23
|
+
* scope. Each caller has a diagnostic view over the shared native reader state;
|
|
24
|
+
* one plugin cannot inherit another plugin's failure label (#1720). Go supplies
|
|
25
|
+
* the workspace's required Go and toolchain directives. These reads preserve
|
|
26
|
+
* the existing installation/version and sequential-stability limits.
|
|
22
27
|
*
|
|
23
28
|
* @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
29
|
* @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
30
|
* @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
31
|
* @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
32
|
* @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
|
|
29
|
-
* @evidence contracts/performance.md#reuse-equivalent-work
|
|
33
|
+
* @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.
|
|
34
|
+
* @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
35
|
* @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
36
|
*/
|
|
32
37
|
export namespace SourcePluginWorkspace {
|
|
@@ -55,7 +60,8 @@ export namespace SourcePluginWorkspace {
|
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
|
-
* Write the native builder's generated workspace and let Go select its
|
|
63
|
+
* Write the native builder's generated workspace and let Go select its
|
|
64
|
+
* version.
|
|
59
65
|
*
|
|
60
66
|
* @evidence contracts/common.md#principled-implementation Actual manifest records drive managed overlay filtering and replacement admission; Go work use supplies required directives.
|
|
61
67
|
* @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 +179,7 @@ export namespace SourcePluginWorkspace {
|
|
|
173
179
|
interface GoModInfo {
|
|
174
180
|
readonly modulePath: string | null;
|
|
175
181
|
readonly replacements: readonly GoModReplacement[];
|
|
182
|
+
readonly directives: NonNullable<GoModJson["Replace"]>;
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
/**
|
|
@@ -190,17 +197,19 @@ export namespace SourcePluginWorkspace {
|
|
|
190
197
|
export interface GoModReader {
|
|
191
198
|
/**
|
|
192
199
|
* Observe the module at this native directory within the reader context.
|
|
200
|
+
* The optional diagnostic owner overrides the creating caller's label only;
|
|
201
|
+
* it changes no selected-tool, environment or byte-content authority.
|
|
193
202
|
*
|
|
194
203
|
* @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
|
|
204
|
+
* @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
205
|
* @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
|
|
206
|
+
* @evidence contracts/common.md#meaningful-documentation Native prose states selected-context result ownership and the optional caller label, which changes failure attribution only.
|
|
198
207
|
* @evidence contracts/portability.md#os-neutral-implementation The directory is a native filesystem path; its resolution belongs to the implementing reader.
|
|
199
208
|
* @evidence contracts/performance.md#efficient-algorithms A method signature executes no algorithm; createGoModReader owns acquisition and parsing.
|
|
200
209
|
* @evidence contracts/performance.md#reuse-equivalent-work Implementations supply observations within their selected tool/environment scope; this signature establishes no cross-context memo.
|
|
201
210
|
* @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
211
|
*/
|
|
203
|
-
read(dir: string): GoModInfo;
|
|
212
|
+
read(dir: string, diagnosticPlugin?: string): GoModInfo;
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
interface GoModJson {
|
|
@@ -224,36 +233,144 @@ export namespace SourcePluginWorkspace {
|
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
/**
|
|
227
|
-
* Read
|
|
236
|
+
* Read current manifest bytes and share identical Go parses within one pinned
|
|
237
|
+
* selected-tool/load context, including copied scratch modules. An optional
|
|
238
|
+
* registry shares native reader state from the first watch query onward; its
|
|
239
|
+
* owner must keep one fixed effective environment and discard it on retry.
|
|
240
|
+
* Caller views share that native state and syntax, while each default
|
|
241
|
+
* diagnostic label remains with its current caller.
|
|
228
242
|
*
|
|
229
|
-
* @evidence contracts/common.md#principled-implementation Actual go mod edit JSON determines module/replacement identities;
|
|
230
|
-
* @evidence contracts/common.md#clear-and-simple-design
|
|
243
|
+
* @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.
|
|
244
|
+
* @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
245
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts No custom Go manifest parser or global context-insensitive memo is introduced.
|
|
232
246
|
* @evidence contracts/common.md#meaningful-documentation Native prose states this operation's authority and delegated boundaries; fields retain separate native comments.
|
|
233
247
|
* @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
|
|
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;
|
|
236
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources The map
|
|
248
|
+
* @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.
|
|
249
|
+
* @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.
|
|
250
|
+
* @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
251
|
*/
|
|
238
252
|
export function createGoModReader(
|
|
239
253
|
goBinary: string,
|
|
240
254
|
pluginName: string,
|
|
241
255
|
env: NodeJS.ProcessEnv,
|
|
256
|
+
readers?: Map<string, GoModReader>,
|
|
242
257
|
): GoModReader {
|
|
258
|
+
const existing = readers?.get(goBinary);
|
|
259
|
+
if (existing !== undefined)
|
|
260
|
+
return {
|
|
261
|
+
read: (dir, diagnosticPlugin = pluginName) =>
|
|
262
|
+
existing.read(dir, diagnosticPlugin),
|
|
263
|
+
};
|
|
264
|
+
// WARNING (#1712): parse identical bytes once in this pinned tool/load
|
|
265
|
+
// context, including early replacement watch projection and copied scratch
|
|
266
|
+
// manifests. The optional registry belongs to one fixed effective environment
|
|
267
|
+
// and one load; it is never shared with a toolchain retry. Always read current bytes;
|
|
268
|
+
// a pathname or metadata-only memo misses manifest edits. Never promote
|
|
269
|
+
// this map to a process cache: tool/environment epochs change Go authority.
|
|
270
|
+
// Fixed variable strings do not freeze GOENV or a dispatched Go executable.
|
|
271
|
+
// Requalify their actual native evidence before every syntax hit; missing
|
|
272
|
+
// environment authority permits a fresh parse only, never positive reuse.
|
|
273
|
+
// WARNING (#1720): diagnostic names are per caller, not a syntax/cache input.
|
|
274
|
+
// Shared views must forward their current label to every failed native read.
|
|
243
275
|
const cache = new Map<string, GoModInfo>();
|
|
244
|
-
|
|
245
|
-
|
|
276
|
+
const environment = { ...env };
|
|
277
|
+
let toolWitness: PluginBuildEnvironmentWitness.Record | undefined;
|
|
278
|
+
const reader: GoModReader = {
|
|
279
|
+
read(dir, diagnosticPlugin = pluginName) {
|
|
246
280
|
OwnedSynchronousProcess.checkpoint();
|
|
247
281
|
const resolved = path.resolve(dir);
|
|
248
|
-
const
|
|
282
|
+
const manifest = path.join(resolved, "go.mod");
|
|
283
|
+
let bytes: Buffer;
|
|
284
|
+
try {
|
|
285
|
+
bytes = fs.readFileSync(manifest);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT")
|
|
288
|
+
return emptyGoModInfo();
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
if (
|
|
292
|
+
toolWitness === undefined ||
|
|
293
|
+
!PluginBuildEnvironmentWitness.holds(toolWitness)
|
|
294
|
+
) {
|
|
295
|
+
cache.clear();
|
|
296
|
+
const witness: PluginBuildEnvironmentWitness.Record = new Map();
|
|
297
|
+
let launcher: fs.BigIntStats | undefined;
|
|
298
|
+
try {
|
|
299
|
+
launcher = fs.statSync(goBinary, { bigint: true });
|
|
300
|
+
} catch {
|
|
301
|
+
// Unavailable metadata cannot qualify optional syntax reuse.
|
|
302
|
+
}
|
|
303
|
+
if (launcher?.isFile() === true)
|
|
304
|
+
PluginBuildEnvironmentWitness.add(witness, goBinary, launcher);
|
|
305
|
+
// GOENV can change toolchain selection without changing env strings.
|
|
306
|
+
// Share its existing native discovery owner; do not scan GOROOT here.
|
|
307
|
+
// Do not launch a missing/unobservable tool twice just to learn that
|
|
308
|
+
// optional cache qualification is unavailable (#1712). The actual
|
|
309
|
+
// mod-edit attempt below still owns its current failure or success.
|
|
310
|
+
const observed =
|
|
311
|
+
launcher?.isFile() === true
|
|
312
|
+
? GoEnvironmentReading.read(
|
|
313
|
+
goBinary,
|
|
314
|
+
os.tmpdir(),
|
|
315
|
+
environment,
|
|
316
|
+
["GOROOT"],
|
|
317
|
+
witness,
|
|
318
|
+
)
|
|
319
|
+
: undefined;
|
|
320
|
+
if (
|
|
321
|
+
typeof observed?.GOROOT === "string" &&
|
|
322
|
+
path.isAbsolute(observed.GOROOT)
|
|
323
|
+
)
|
|
324
|
+
PluginBuildEnvironmentWitness.add(
|
|
325
|
+
witness,
|
|
326
|
+
path.join(
|
|
327
|
+
observed.GOROOT,
|
|
328
|
+
"bin",
|
|
329
|
+
process.platform === "win32" ? "go.exe" : "go",
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
const complete =
|
|
333
|
+
typeof observed?.GOROOT === "string" &&
|
|
334
|
+
path.isAbsolute(observed.GOROOT) &&
|
|
335
|
+
typeof observed.GOENV === "string" &&
|
|
336
|
+
(observed.GOENV === "off" || path.isAbsolute(observed.GOENV));
|
|
337
|
+
toolWitness =
|
|
338
|
+
complete && PluginBuildEnvironmentWitness.holds(witness)
|
|
339
|
+
? witness
|
|
340
|
+
: undefined;
|
|
341
|
+
}
|
|
342
|
+
// UTF-8 decoding is lossy for malformed input. Only identical bytes
|
|
343
|
+
// can share Go syntax, even when Go serializes two inputs identically.
|
|
344
|
+
const byteKey = bytes.toString("base64");
|
|
345
|
+
const cached = cache.get(byteKey);
|
|
249
346
|
if (cached !== undefined) {
|
|
250
347
|
return cached;
|
|
251
348
|
}
|
|
252
|
-
const info = readGoModInfo(
|
|
253
|
-
|
|
349
|
+
const info = readGoModInfo(
|
|
350
|
+
resolved,
|
|
351
|
+
goBinary,
|
|
352
|
+
diagnosticPlugin,
|
|
353
|
+
environment,
|
|
354
|
+
);
|
|
355
|
+
if (!fs.readFileSync(manifest).equals(bytes))
|
|
356
|
+
throw new Error(
|
|
357
|
+
`ttsc: go.mod changed while Go was reading ${manifest}`,
|
|
358
|
+
);
|
|
359
|
+
if (
|
|
360
|
+
toolWitness !== undefined &&
|
|
361
|
+
!PluginBuildEnvironmentWitness.holds(toolWitness)
|
|
362
|
+
)
|
|
363
|
+
throw new Error(
|
|
364
|
+
`ttsc: Go tool or environment changed while reading ${manifest}`,
|
|
365
|
+
);
|
|
366
|
+
// An unavailable environment observation can still execute a fresh Go
|
|
367
|
+
// parse, but it cannot authorize sharing that result on another call.
|
|
368
|
+
if (toolWitness !== undefined) cache.set(byteKey, info);
|
|
254
369
|
return info;
|
|
255
370
|
},
|
|
256
371
|
};
|
|
372
|
+
readers?.set(goBinary, reader);
|
|
373
|
+
return reader;
|
|
257
374
|
}
|
|
258
375
|
|
|
259
376
|
function readGoModInfo(
|
|
@@ -313,6 +430,7 @@ export namespace SourcePluginWorkspace {
|
|
|
313
430
|
|
|
314
431
|
return {
|
|
315
432
|
modulePath: json.Module?.Path ?? null,
|
|
433
|
+
directives: json.Replace ?? [],
|
|
316
434
|
replacements: (json.Replace ?? [])
|
|
317
435
|
.map(jsonReplacementToGoModReplacement)
|
|
318
436
|
.filter((replacement) => replacement !== null),
|
|
@@ -322,6 +440,7 @@ export namespace SourcePluginWorkspace {
|
|
|
322
440
|
function emptyGoModInfo(): GoModInfo {
|
|
323
441
|
return {
|
|
324
442
|
modulePath: null,
|
|
443
|
+
directives: [],
|
|
325
444
|
replacements: [],
|
|
326
445
|
};
|
|
327
446
|
}
|
|
@@ -441,6 +560,5 @@ export namespace SourcePluginWorkspace {
|
|
|
441
560
|
}
|
|
442
561
|
}
|
|
443
562
|
|
|
444
|
-
|
|
445
563
|
const TTSC_GO_MODULE_PATH = "github.com/samchon/ttsc/packages/ttsc";
|
|
446
564
|
}
|