ttsc 0.35.0 → 0.36.0
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/driver/compilation_snapshot.go +267 -0
- package/driver/compilation_snapshot_keeps_inputs_across_stages_test.go +130 -0
- package/driver/compilation_snapshot_preserves_checked_emission_test.go +100 -0
- package/driver/graph.go +9 -3
- package/driver/program.go +45 -14
- package/lib/compiler/internal/CompilationSnapshot.d.ts +33 -0
- package/lib/compiler/internal/CompilationSnapshot.js +90 -0
- package/lib/compiler/internal/CompilationSnapshot.js.map +1 -0
- package/lib/compiler/internal/build/BuildExecution.d.ts +7 -4
- package/lib/compiler/internal/build/BuildExecution.js +68 -5
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/build/appendBuildOutput.js +4 -0
- package/lib/compiler/internal/build/appendBuildOutput.js.map +1 -1
- package/lib/compiler/internal/build/runExternalEmitProvenance.d.ts +12 -13
- package/lib/compiler/internal/build/runExternalEmitProvenance.js +24 -18
- package/lib/compiler/internal/build/runExternalEmitProvenance.js.map +1 -1
- package/lib/compiler/internal/parseNativeTransformOutput.js +4 -0
- package/lib/compiler/internal/parseNativeTransformOutput.js.map +1 -1
- package/lib/compiler/internal/sharedHost/inheritedSidecarEnv.d.ts +2 -1
- package/lib/compiler/internal/sharedHost/inheritedSidecarEnv.js +4 -1
- package/lib/compiler/internal/sharedHost/inheritedSidecarEnv.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -3
- package/lib/compiler/internal/transformProjectInMemory.js +29 -5
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +5 -0
- package/package.json +8 -8
- package/src/compiler/internal/CompilationSnapshot.ts +97 -0
- package/src/compiler/internal/build/BuildExecution.ts +96 -4
- package/src/compiler/internal/build/appendBuildOutput.ts +4 -0
- package/src/compiler/internal/build/runExternalEmitProvenance.ts +25 -23
- package/src/compiler/internal/parseNativeTransformOutput.ts +5 -0
- package/src/compiler/internal/sharedHost/inheritedSidecarEnv.ts +8 -1
- package/src/compiler/internal/transformProjectInMemory.ts +46 -5
- package/src/structures/ITtscCompilerTransformation.ts +6 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { createCanonicalTempDirectory } from "../../internal/createCanonicalTempDirectory";
|
|
6
|
+
import { SidecarEnvironment } from "./sharedHost/SidecarEnvironment";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Own one read-once compiler view across sequential native stages. Its private
|
|
10
|
+
* document stores actual compiler text and lookup answers, including absence;
|
|
11
|
+
* each invocation starts empty and removes its storage after its final stage.
|
|
12
|
+
* Disk freshness remains a separate cache-admission decision.
|
|
13
|
+
*
|
|
14
|
+
* @evidence contracts/common.md#principled-implementation A unique invocation ID and versioned private document share first-read answers through the driver's supported VFS, without changing source paths or the selected compiler.
|
|
15
|
+
* @evidence contracts/common.md#clear-and-simple-design One synchronous owner brackets the sequential compiler operation and its transport cleanup.
|
|
16
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The driver consumes real captured text; no filesystem methods or compiler internals are replaced.
|
|
17
|
+
* @evidence contracts/common.md#meaningful-documentation Prose states read-once semantics, sequential stage ownership, absence and independent freshness admission.
|
|
18
|
+
* @evidence contracts/portability.md#os-neutral-implementation Native temporary-directory creation and path APIs own transport; environment merging preserves Windows key semantics.
|
|
19
|
+
* @evidence contracts/performance.md#efficient-algorithms Construction writes an empty document; each native stage parses prior captured text/predicates and dirty flushes encode and write the complete reached population. Normal UTF-8 remains plain text, while invalid UTF-8 is encoded losslessly and decoded once per stage.
|
|
20
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Only stages of this invocation share captured answers; unrelated invocations have fresh IDs and documents.
|
|
21
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources The synchronous owner attempts identity-checked cleanup after success or failure; native captured bytes have no population cap. A failed initial identity read or cleanup can leave storage behind; cleanup failure retains the original thrown error or completed result.
|
|
22
|
+
*/
|
|
23
|
+
export namespace CompilationSnapshot {
|
|
24
|
+
export const ENV = "TTSC_COMPILATION_SNAPSHOT";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run sequential native stages with a new invocation-owned input document.
|
|
28
|
+
* The supplied environment is copied; same-directory identity guards cleanup,
|
|
29
|
+
* and cleanup failure retains the operation error or completed result.
|
|
30
|
+
*
|
|
31
|
+
* @evidence contracts/common.md#principled-implementation A unique invocation ID and versioned private document share first-read answers through the driver's supported VFS, without changing source paths or the selected compiler.
|
|
32
|
+
* @evidence contracts/common.md#clear-and-simple-design One synchronous owner brackets the sequential compiler operation and its transport cleanup.
|
|
33
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts The driver consumes real captured text; no filesystem methods or compiler internals are replaced.
|
|
34
|
+
* @evidence contracts/common.md#meaningful-documentation Prose states read-once semantics, sequential stage ownership, absence and independent freshness admission.
|
|
35
|
+
* @evidence contracts/portability.md#os-neutral-implementation Native temporary-directory creation and path APIs own transport; environment merging preserves Windows key semantics.
|
|
36
|
+
* @evidence contracts/performance.md#efficient-algorithms Construction writes an empty document; each native stage parses prior captured text/predicates and dirty flushes encode and write the complete reached population. Normal UTF-8 remains plain text, while invalid UTF-8 is encoded losslessly and decoded once per stage.
|
|
37
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Only stages of this invocation share captured answers; unrelated invocations have fresh IDs and documents.
|
|
38
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources The synchronous owner attempts identity-checked cleanup after success or failure; native captured bytes have no population cap. A failed initial identity read or cleanup can leave storage behind; cleanup failure retains the original thrown error or completed result.
|
|
39
|
+
*/
|
|
40
|
+
export function run<T>(
|
|
41
|
+
environment: NodeJS.ProcessEnv | undefined,
|
|
42
|
+
operation: (environment: NodeJS.ProcessEnv) => T,
|
|
43
|
+
): T {
|
|
44
|
+
const directory = createCanonicalTempDirectory("ttsc-input-snapshot-");
|
|
45
|
+
const ownership = fs.lstatSync(directory);
|
|
46
|
+
let completed: T | undefined;
|
|
47
|
+
let failed = false;
|
|
48
|
+
let failure: unknown;
|
|
49
|
+
try {
|
|
50
|
+
const file = path.join(directory, "inputs.json");
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
file,
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
version: 1,
|
|
55
|
+
id: crypto.randomUUID(),
|
|
56
|
+
reads: {},
|
|
57
|
+
files: {},
|
|
58
|
+
directories: {},
|
|
59
|
+
entries: {},
|
|
60
|
+
realpaths: {},
|
|
61
|
+
stats: {},
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
const env = SidecarEnvironment.merge(environment, { [ENV]: file });
|
|
65
|
+
completed = operation(env);
|
|
66
|
+
return completed;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
failed = true;
|
|
69
|
+
failure = error;
|
|
70
|
+
throw error;
|
|
71
|
+
} finally {
|
|
72
|
+
try {
|
|
73
|
+
const current = fs.lstatSync(directory);
|
|
74
|
+
if (
|
|
75
|
+
!current.isDirectory() ||
|
|
76
|
+
current.isSymbolicLink() ||
|
|
77
|
+
current.dev !== ownership.dev ||
|
|
78
|
+
current.ino !== ownership.ino
|
|
79
|
+
)
|
|
80
|
+
throw new Error("Compilation snapshot directory ownership changed");
|
|
81
|
+
fs.rmSync(directory, { recursive: true });
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (failed)
|
|
84
|
+
throw new AggregateError(
|
|
85
|
+
[failure, error],
|
|
86
|
+
"Compilation snapshot and cleanup failed",
|
|
87
|
+
{ cause: failure },
|
|
88
|
+
);
|
|
89
|
+
throw new AggregateError(
|
|
90
|
+
[completed, error],
|
|
91
|
+
"Compilation completed but snapshot cleanup failed",
|
|
92
|
+
{ cause: completed },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -13,6 +13,8 @@ import type { TtscBuildOptions } from "../../../structures/internal/TtscBuildOpt
|
|
|
13
13
|
import type { TtscBuildResult } from "../../../structures/internal/TtscBuildResult";
|
|
14
14
|
import type { TtscCommonOptions } from "../../../structures/internal/TtscCommonOptions";
|
|
15
15
|
import type { TtscPluginSourceInputsHandler } from "../../../structures/internal/TtscSingleFileEmitOptions";
|
|
16
|
+
import { CompilationSnapshot } from "../CompilationSnapshot";
|
|
17
|
+
import { compileProjectInMemory } from "../compileProjectInMemory";
|
|
16
18
|
import { outputText } from "../outputText";
|
|
17
19
|
import { CompilerProjectSelection } from "../project/CompilerProjectSelection";
|
|
18
20
|
import { readProjectConfig } from "../project/readProjectConfig";
|
|
@@ -226,6 +228,11 @@ export namespace BuildExecution {
|
|
|
226
228
|
clearInheritedTsgoArgs(env, extra);
|
|
227
229
|
}
|
|
228
230
|
clearInheritedSemanticConfigPath(env, extra);
|
|
231
|
+
SidecarEnvironment.write(
|
|
232
|
+
env,
|
|
233
|
+
CompilationSnapshot.ENV,
|
|
234
|
+
SidecarEnvironment.read(extra, CompilationSnapshot.ENV),
|
|
235
|
+
);
|
|
229
236
|
// The anchor is per-invocation state owned by this host: when this run
|
|
230
237
|
// declared none (and the caller's env does not name one), drop any value
|
|
231
238
|
// inherited from an ancestor ttsc process so a nested build never
|
|
@@ -363,22 +370,45 @@ export namespace BuildExecution {
|
|
|
363
370
|
* opted-in native host reports its captured generation, while the direct
|
|
364
371
|
* compiler adapter admits only a stable observed selection and supported
|
|
365
372
|
* output layout. Unknown ownership remains explicit for consumers rather than
|
|
366
|
-
* guessed.
|
|
373
|
+
* guessed. Native runtime stages share one invocation-owned read-once view;
|
|
374
|
+
* configured checks followed by the exact installed compiler emit through its
|
|
375
|
+
* matching native target, while custom compiler executables remain
|
|
376
|
+
* delegated.
|
|
367
377
|
*
|
|
368
378
|
* @evidence contracts/common.md#principled-implementation Ordered checks block emit on failure and retain negotiated same-generation input observations for the actual check verb; effectful fix/format results keep their distinct command contract without fabricated reusable check metadata; transforms use one compatible host. Normally completed native partial emission and supported stable external emission can retain actual-write provenance alongside unchanged nonzero status, while interrupted or unknown generations establish no proof.
|
|
369
379
|
* @evidence contracts/common.md#clear-and-simple-design This dispatcher owns phase policy while argv, spawning, normalization and fallback comparison remain shared helpers used by one-shot and watch lanes.
|
|
370
380
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Format bypass follows its write-only contract; capability-based diagnostics/provenance and failure fallback preserve supported producer semantics without plugin-name shortcuts, foreign mutation or filename-based ownership guesses.
|
|
371
381
|
* @evidence contracts/common.md#meaningful-documentation Native prose states ordering, format effects and thrown boundary failures; branch comments explain nonobvious emission and display policies.
|
|
372
382
|
* @evidence contracts/portability.md#os-neutral-implementation Commands preserve native executable/argv/cwd boundaries; provenance uses a canonical unique temporary directory and absolute native wire paths without post-compile realpath reconstruction or OS-derived case folding.
|
|
373
|
-
* @evidence contracts/performance.md#efficient-algorithms Plugin/stage selection scans the configured population; each command includes argv/env publication and native runtime probes under its helpers. Required proof adds artifact or external inspection/JSON/source-executable observation work, plus association/path processing. Accumulating phase reports can recopy earlier bytes; there is no supplied bound on total reports, proof bytes or child duration.
|
|
374
|
-
* @evidence contracts/performance.md#reuse-equivalent-work A check host declaring TypeScript diagnostics avoids an equivalent extra successful check, while compatible transform plugins share one host; effectful configured checks still execute in order.
|
|
375
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources Phase results and command/proof records stay build-local and returned data transfer to the caller, with uncapped report/association bytes. Native proof setup observes ownership before finally, so setup failure can leave allocation unclaimed; accepted artifacts reach identity-checked cleanup attempts. Replaced parents remain untouched. Cleanup aggregates an earlier throw or nonzero result; other cleanup errors propagate without certifying release. Delegated capture/descendant release remains with its owner; no resident history is retained here.
|
|
383
|
+
* @evidence contracts/performance.md#efficient-algorithms Plugin/stage selection scans the configured population; each command includes argv/env publication and native runtime probes under its helpers. Required proof adds artifact or external inspection/JSON/source-executable observation work, plus association/path processing. Native runtime snapshot stages parse the preceding full document and dirty stages serialize/write its complete captured population. Accumulating phase reports can recopy earlier bytes; there is no supplied bound on total reports, proof bytes or child duration.
|
|
384
|
+
* @evidence contracts/performance.md#reuse-equivalent-work A check host declaring TypeScript diagnostics avoids an equivalent extra successful check, while compatible transform plugins share one host; effectful configured checks still execute in order. Captured answers are shared only across the sequential native stages of this invocation, independently of later cache admission.
|
|
385
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources Phase results and command/proof records stay build-local and returned data transfer to the caller, with uncapped report/association bytes. Native proof setup observes ownership before finally, so setup failure can leave allocation unclaimed; accepted artifacts reach identity-checked cleanup attempts. Replaced parents remain untouched. Cleanup aggregates an earlier throw or nonzero result; other cleanup errors propagate without certifying release. The snapshot coordinator attempts identity-checked transport cleanup and retains an original error or completed result when cleanup fails. Captured text and full document buffers follow reached input bytes without a cap. Delegated capture/descendant release remains with its owner; no resident history is retained here.
|
|
376
386
|
*/
|
|
377
387
|
export function runPreparedBuild(
|
|
378
388
|
options: RunBuildOptions,
|
|
379
389
|
timing: BuildTiming.BuildTiming,
|
|
380
390
|
execution: ReturnType<typeof resolveExecutionContext>,
|
|
381
391
|
buildOptions: RunBuildOptions,
|
|
392
|
+
): TtscBuildResult {
|
|
393
|
+
if (
|
|
394
|
+
buildOptions.forceEmitProvenance === true &&
|
|
395
|
+
execution.nativePlugins.length !== 0
|
|
396
|
+
)
|
|
397
|
+
return CompilationSnapshot.run(buildOptions.env, (env) =>
|
|
398
|
+
runPreparedSnapshotBuild(options, timing, execution, {
|
|
399
|
+
...buildOptions,
|
|
400
|
+
env,
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
return runPreparedSnapshotBuild(options, timing, execution, buildOptions);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Execute one already selected build without replacing compiler authority. */
|
|
407
|
+
function runPreparedSnapshotBuild(
|
|
408
|
+
options: RunBuildOptions,
|
|
409
|
+
timing: BuildTiming.BuildTiming,
|
|
410
|
+
execution: ReturnType<typeof resolveExecutionContext>,
|
|
411
|
+
buildOptions: RunBuildOptions,
|
|
382
412
|
): TtscBuildResult {
|
|
383
413
|
if (execution.compilerSelection.inspectionError !== undefined) {
|
|
384
414
|
const diagnosed = runTsgo(execution, [], buildOptions);
|
|
@@ -463,6 +493,14 @@ export namespace BuildExecution {
|
|
|
463
493
|
execution,
|
|
464
494
|
);
|
|
465
495
|
}
|
|
496
|
+
} else if (
|
|
497
|
+
buildOptions.forceEmitProvenance === true &&
|
|
498
|
+
isProjectCompiler(execution, buildOptions)
|
|
499
|
+
) {
|
|
500
|
+
result = appendBuildOutput(
|
|
501
|
+
checked,
|
|
502
|
+
runSnapshotCompilerBuild(execution, buildOptions),
|
|
503
|
+
);
|
|
466
504
|
} else {
|
|
467
505
|
if (
|
|
468
506
|
buildOptions.skipDiagnosticsCheck !== true &&
|
|
@@ -979,6 +1017,60 @@ export namespace BuildExecution {
|
|
|
979
1017
|
);
|
|
980
1018
|
}
|
|
981
1019
|
|
|
1020
|
+
/**
|
|
1021
|
+
* Preserve custom compiler executables while recognizing the project's exact
|
|
1022
|
+
* compiler.
|
|
1023
|
+
*/
|
|
1024
|
+
function isProjectCompiler(
|
|
1025
|
+
execution: ReturnType<typeof resolveExecutionContext>,
|
|
1026
|
+
options: RunBuildOptions,
|
|
1027
|
+
): boolean {
|
|
1028
|
+
if (execution.tsgo.version !== "custom") return true;
|
|
1029
|
+
try {
|
|
1030
|
+
const env = SidecarEnvironment.merge(process.env, options.env, {
|
|
1031
|
+
TTSC_TSGO_BINARY: undefined,
|
|
1032
|
+
});
|
|
1033
|
+
const installed = resolveTsgo({ cwd: execution.projectRoot, env });
|
|
1034
|
+
return (
|
|
1035
|
+
fs.realpathSync.native(installed.binary) ===
|
|
1036
|
+
fs.realpathSync.native(execution.tsgo.binary)
|
|
1037
|
+
);
|
|
1038
|
+
} catch {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Emit through the selected upstream target's native VFS after native checks. */
|
|
1044
|
+
function runSnapshotCompilerBuild(
|
|
1045
|
+
execution: ReturnType<typeof resolveExecutionContext>,
|
|
1046
|
+
options: RunBuildOptions,
|
|
1047
|
+
): TtscBuildResult {
|
|
1048
|
+
const forwarded = TsgoArguments.createNativeBuildTsgoArgs(
|
|
1049
|
+
execution,
|
|
1050
|
+
options,
|
|
1051
|
+
);
|
|
1052
|
+
const env = SidecarEnvironment.merge(options.env, {
|
|
1053
|
+
[TSGO_ARGS_ENV]: JSON.stringify(forwarded),
|
|
1054
|
+
[TSGO_ARGS_CWD_ENV]: execution.compilerSelection.compilerArgsCwd,
|
|
1055
|
+
});
|
|
1056
|
+
const compiled = compileProjectInMemory({
|
|
1057
|
+
cwd: execution.projectRoot,
|
|
1058
|
+
projectRoot: execution.projectRoot,
|
|
1059
|
+
tsconfig: execution.tsconfig,
|
|
1060
|
+
env,
|
|
1061
|
+
plugins: [],
|
|
1062
|
+
cacheDir: options.cacheDir,
|
|
1063
|
+
});
|
|
1064
|
+
const emittedFiles: string[] = [];
|
|
1065
|
+
for (const [name, text] of Object.entries(compiled.output)) {
|
|
1066
|
+
const output = path.resolve(execution.projectRoot, name);
|
|
1067
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
1068
|
+
fs.writeFileSync(output, text);
|
|
1069
|
+
emittedFiles.push(output);
|
|
1070
|
+
}
|
|
1071
|
+
return { ...compiled.result, emittedFiles };
|
|
1072
|
+
}
|
|
1073
|
+
|
|
982
1074
|
/**
|
|
983
1075
|
* Run `tsgo` with the full emit arguments and parse `TSFILE:` lines from
|
|
984
1076
|
* stdout into `emittedFiles`. Internally requested TSFILE lines are stripped;
|
|
@@ -186,6 +186,10 @@ export function mergeCheckGraphs(
|
|
|
186
186
|
observations[key] = merged;
|
|
187
187
|
}
|
|
188
188
|
return {
|
|
189
|
+
...(left.compilationSnapshot !== undefined &&
|
|
190
|
+
left.compilationSnapshot === right.compilationSnapshot
|
|
191
|
+
? { compilationSnapshot: left.compilationSnapshot }
|
|
192
|
+
: {}),
|
|
189
193
|
edges: mergeLists(left.edges, right.edges),
|
|
190
194
|
...(membershipConflict ||
|
|
191
195
|
(left.membership ?? right.membership) === undefined
|
|
@@ -17,17 +17,16 @@ import { PassthroughFlags } from "./PassthroughFlags";
|
|
|
17
17
|
* diagnostics and user-requested file-list display remain owned by that run.
|
|
18
18
|
*
|
|
19
19
|
* Admission requires stable effective options, identical source selection and
|
|
20
|
-
* stable physical paths
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* list.
|
|
20
|
+
* stable physical source paths before and after emission, plus unchanged
|
|
21
|
+
* response files and selected executable bytes. Response inspection preserves
|
|
22
|
+
* native argument-frame boundaries and operand positions; the producer and its
|
|
23
|
+
* probes receive the original argv. The executable's change time is excluded
|
|
24
|
+
* from cross-command equality because that metadata changed in real executions
|
|
25
|
+
* without changing the executable object or bytes; source contents belong to
|
|
26
|
+
* the compiler's own Program. Each executable read brackets its full metadata.
|
|
27
|
+
* This is an external observation, not an atomic compiler-generation ledger; a
|
|
28
|
+
* concurrent change restored between observations can remain invisible.
|
|
29
|
+
* Unsupported layouts and ambiguous outputs carry an empty ownership list.
|
|
31
30
|
*
|
|
32
31
|
* A nonzero compiler status can still carry proved writes for an unchecked
|
|
33
32
|
* consumer. Ownership metadata never changes that status or its diagnostics.
|
|
@@ -47,12 +46,12 @@ import { PassthroughFlags } from "./PassthroughFlags";
|
|
|
47
46
|
* in emittedSourceProofFailures. These explanations preserve compiler streams
|
|
48
47
|
* and status; they do not replace missing ownership with a successful claim.
|
|
49
48
|
*
|
|
50
|
-
* @evidence contracts/common.md#principled-implementation Actual TSFILE writes are matched against the supported upstream per-source extension and explicit-root layout under matching compiler-selected inputs and stable physical/content observations. No emitted filename alone establishes ownership; observation gaps remain documented.
|
|
49
|
+
* @evidence contracts/common.md#principled-implementation Actual TSFILE writes are matched against the supported upstream per-source extension and explicit-root layout under matching compiler-selected inputs and stable physical source identities and response/executable content observations. No emitted filename alone establishes ownership; observation gaps remain documented.
|
|
51
50
|
* @evidence contracts/common.md#clear-and-simple-design One adapter owns read-only compiler probes, the single delegated emission and admission; private helpers isolate option parsing, physical observation and path prediction.
|
|
52
51
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts The selected executable and original arguments remain authoritative. Unknown layouts are reported unknown instead of switching compilers, guessing same-stem ownership or consulting source maps.
|
|
53
52
|
* @evidence contracts/common.md#meaningful-documentation Native paragraphs explain producer preservation, unknown states and the non-atomic observation limitation; the investigation record documents the supported upstream naming subset.
|
|
54
53
|
* @evidence contracts/portability.md#os-neutral-implementation Native node:path and filesystem realpath/stat separate writer spelling from physical source identity. Executables receive separate argv entries through spawnNative, without a shell or OS-based case folding.
|
|
55
|
-
* @evidence contracts/performance.md#efficient-algorithms Source and output indexes avoid cross-product matching. Response inspection uses an iterative frame stack and active physical-path set.
|
|
54
|
+
* @evidence contracts/performance.md#efficient-algorithms Source and output indexes avoid cross-product matching. Response inspection uses an iterative frame stack and active physical-path set. Response/executable observations read and hash complete files; source identity checks resolve paths without comparing mutable text; showConfig text, source/output lists, signature JSON, candidate path components and byte buffers are processed at their actual sizes alongside native probes/path operations. No native duration or report-size ceiling follows from using one index.
|
|
56
55
|
* @evidence contracts/performance.md#reuse-equivalent-work Each source observation and predicted output index is shared by all outputs in this invocation. Cross-build reuse is unavailable because arbitrary external producer and filesystem dependencies have no invalidation contract.
|
|
57
56
|
* @evidence contracts/performance.md#bound-retention-and-release-resources Response/config/source tokens and predicted owner sets live for this invocation; returned provenance/refusal records transfer to the caller. Retained bytes grow with complete reports, expanded arguments, paths and observations, while individual file reads allocate full file buffers. Probe capture cleanup is delegated and may be best effort; no timeout, output ceiling or descendant-release certification is provided here.
|
|
58
57
|
*/
|
|
@@ -144,7 +143,7 @@ export function runExternalEmitProvenance(options: {
|
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
// An observed host lists nothing; it compiles the same selected tsconfig, and
|
|
147
|
-
// the source observations below still prove that selection
|
|
146
|
+
// the source identity observations below still prove that selection retained its physical owners.
|
|
148
147
|
const listed = observed
|
|
149
148
|
? probe.files
|
|
150
149
|
: compilerSourceList(lines, options.cwd);
|
|
@@ -185,10 +184,10 @@ export function runExternalEmitProvenance(options: {
|
|
|
185
184
|
for (const [source, before] of probe.observations) {
|
|
186
185
|
if (!stable) break;
|
|
187
186
|
stage = `source observation of ${JSON.stringify(source)}`;
|
|
188
|
-
const after =
|
|
187
|
+
const after = observeSourceIdentity(source);
|
|
189
188
|
stable = after === before;
|
|
190
189
|
if (!stable)
|
|
191
|
-
failure = `Selected source changed across emission: ${JSON.stringify(source)}
|
|
190
|
+
failure = `Selected source identity changed across emission: ${JSON.stringify(source)}.`;
|
|
192
191
|
}
|
|
193
192
|
for (const [file, before] of probe.responseObservations) {
|
|
194
193
|
if (!stable) break;
|
|
@@ -207,9 +206,7 @@ export function runExternalEmitProvenance(options: {
|
|
|
207
206
|
if (outputs.length === 0) continue;
|
|
208
207
|
predictedSources++;
|
|
209
208
|
sourceExample ??= source;
|
|
210
|
-
const physicalSource
|
|
211
|
-
probe.observations.get(source)!,
|
|
212
|
-
)[0];
|
|
209
|
+
const physicalSource = probe.observations.get(source)!;
|
|
213
210
|
for (const output of outputs) {
|
|
214
211
|
const sources = owners.get(output) ?? new Set<string>();
|
|
215
212
|
sources.add(physicalSource);
|
|
@@ -368,10 +365,7 @@ function prepareProbe(
|
|
|
368
365
|
const observations = new Map<string, string>();
|
|
369
366
|
for (const source of files) {
|
|
370
367
|
stage = `initial source observation of ${JSON.stringify(source)}`;
|
|
371
|
-
observations.set(
|
|
372
|
-
source,
|
|
373
|
-
CompilerArgumentsInspection.observeInputFile(source),
|
|
374
|
-
);
|
|
368
|
+
observations.set(source, observeSourceIdentity(source));
|
|
375
369
|
}
|
|
376
370
|
for (const [file, before] of inspected.observations)
|
|
377
371
|
if (CompilerArgumentsInspection.observeInputFile(file) !== before)
|
|
@@ -644,3 +638,11 @@ function writtenJavaScript(stdout: string, cwd: string): string[] {
|
|
|
644
638
|
}
|
|
645
639
|
return [...outputs];
|
|
646
640
|
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Source ownership is a physical path; text freshness belongs to cache
|
|
644
|
+
* admission.
|
|
645
|
+
*/
|
|
646
|
+
function observeSourceIdentity(source: string): string {
|
|
647
|
+
return fs.realpathSync.native(source);
|
|
648
|
+
}
|
|
@@ -339,6 +339,7 @@ export function parseReferenceGraph(
|
|
|
339
339
|
return undefined;
|
|
340
340
|
}
|
|
341
341
|
const section = value as {
|
|
342
|
+
compilationSnapshot?: unknown;
|
|
342
343
|
candidates?: unknown;
|
|
343
344
|
configs?: unknown;
|
|
344
345
|
edges?: unknown;
|
|
@@ -420,6 +421,10 @@ export function parseReferenceGraph(
|
|
|
420
421
|
}
|
|
421
422
|
return {
|
|
422
423
|
...(Object.keys(candidates).length === 0 ? {} : { candidates }),
|
|
424
|
+
...(typeof section.compilationSnapshot === "string" &&
|
|
425
|
+
section.compilationSnapshot.length !== 0
|
|
426
|
+
? { compilationSnapshot: section.compilationSnapshot }
|
|
427
|
+
: {}),
|
|
423
428
|
configs,
|
|
424
429
|
edges,
|
|
425
430
|
globals,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CompilationSnapshot } from "../CompilationSnapshot";
|
|
1
2
|
import { SidecarEnvironment } from "./SidecarEnvironment";
|
|
2
3
|
import { clearInheritedSemanticConfigPath } from "./clearInheritedSemanticConfigPath";
|
|
3
4
|
import { clearInheritedTsgoArgs } from "./clearInheritedTsgoArgs";
|
|
@@ -6,7 +7,8 @@ import { publishLinkedTransformPlugins } from "./publishLinkedTransformPlugins";
|
|
|
6
7
|
/**
|
|
7
8
|
* Merge inherited and caller environments with native variable-name identity,
|
|
8
9
|
* then clear per-invocation payloads this lane never published: forwarded tsgo
|
|
9
|
-
* arguments, semantic config ownership and the
|
|
10
|
+
* arguments, compilation snapshot, semantic config ownership and the
|
|
11
|
+
* linked-plugin manifest.
|
|
10
12
|
*
|
|
11
13
|
* Use it at every spawn whose child can reach `driver.LoadProgram` — the
|
|
12
14
|
* `api-compile` / `api-transform` hosts, and the plugin loader, whose `ttsx`
|
|
@@ -40,6 +42,11 @@ export function inheritedSidecarEnv(
|
|
|
40
42
|
);
|
|
41
43
|
clearInheritedTsgoArgs(env, callerEnv);
|
|
42
44
|
clearInheritedSemanticConfigPath(env, callerEnv);
|
|
45
|
+
SidecarEnvironment.write(
|
|
46
|
+
env,
|
|
47
|
+
CompilationSnapshot.ENV,
|
|
48
|
+
SidecarEnvironment.read(callerEnv, CompilationSnapshot.ENV),
|
|
49
|
+
);
|
|
43
50
|
publishLinkedTransformPlugins(env, callerEnv, []);
|
|
44
51
|
return env;
|
|
45
52
|
}
|
|
@@ -11,6 +11,7 @@ import type { ITtscCompilerTransformation } from "../../structures/ITtscCompiler
|
|
|
11
11
|
import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
|
|
12
12
|
import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
|
|
13
13
|
import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
|
|
14
|
+
import { CompilationSnapshot } from "./CompilationSnapshot";
|
|
14
15
|
import { appendBuildOutput, mergeCheckGraphs } from "./build/appendBuildOutput";
|
|
15
16
|
import { normalizeBuildOutput } from "./build/normalizeBuildOutput";
|
|
16
17
|
import { buildNativeCompiler } from "./buildNativeCompiler";
|
|
@@ -60,10 +61,10 @@ import { spawnNative } from "./spawnNative";
|
|
|
60
61
|
* @evidence contracts/common.md#clear-and-simple-design One router composes built-in and plugin-backed transforms; envelope parsers, environment construction, proof merging and negative-only observation limits have separate shared helpers.
|
|
61
62
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts Missing input proof cannot be repaired by a postcompile baseline; conventional deferred config reads accept only the actual native consumer's proof, while malformed required TypeScript output is a protocol error.
|
|
62
63
|
* @evidence contracts/common.md#meaningful-documentation Native paragraphs describe transform lanes and stage ordering; separated result members distinguish output, advisory graph/dependency data, evaluation witnesses and unstable source markers under documentation guidance.
|
|
63
|
-
* @evidence contracts/performance.md#efficient-algorithms Stage filtering, declared-input Sets and proof maps scale with plugin/input counts; merged paths are sorted with path-text comparison costs. Each stage can rehash/re-realpath declared inputs, serialize plugin/config records and parse complete output; discovery, native capability probes, source builds and child execution remain delegated costs of this call. No byte/work ceiling or measured dominant-cost ranking is asserted.
|
|
64
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Source-plugin artifacts are shared by their owning loader; this transform intentionally produces a current generation and gives downstream consumers the graph, source states and host witnesses required to qualify reuse.
|
|
64
|
+
* @evidence contracts/performance.md#efficient-algorithms Stage filtering, declared-input Sets and proof maps scale with plugin/input counts; merged paths are sorted with path-text comparison costs. Each stage can rehash/re-realpath declared inputs, serialize plugin/config records and parse complete output; discovery, native capability probes, source builds and child execution remain delegated costs of this call. The invocation snapshot adds full captured-population parsing and dirty document encoding/writes per native stage. No byte/work ceiling or measured dominant-cost ranking is asserted.
|
|
65
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Descriptor/source discovery precedes snapshot allocation so invocation-private paths do not vary its environment cache identity. Source-plugin artifacts are shared by their owning loader; this transform intentionally produces a current generation and gives downstream consumers the graph, source states and host witnesses required to qualify reuse.
|
|
65
66
|
*
|
|
66
|
-
* @evidence contracts/performance.md#bound-retention-and-release-resources This router retains discovery records, per-stage proof maps and accumulated diagnostics/output until synchronous stage completion, then transfers the returned envelope.
|
|
67
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources This router retains discovery records, per-stage proof maps and accumulated diagnostics/output until synchronous stage completion, then transfers the returned envelope. The invocation coordinator attempts identity-checked snapshot removal and propagates or aggregates removal failure. Other native capture/observation owners attempt their own cleanup; suppressed cleanup failures and descendant release remain unconfirmed where those owners do not join them. Artifact-cache retention belongs to the builder, and no stage timeout or input/output byte ceiling is supplied here.
|
|
67
68
|
*
|
|
68
69
|
* @evidence contracts/portability.md#os-neutral-implementation Native paths remain filesystem coordinates, child argv are arrays, and environment helpers preserve Windows key equivalence and explicit Node/tsgo/config anchors without shell parsing or native method patching.
|
|
69
70
|
*/
|
|
@@ -123,6 +124,16 @@ export function transformProjectInMemory(options: ITtscCompilerContext): {
|
|
|
123
124
|
projectRoot: options.projectRoot,
|
|
124
125
|
tsconfig: options.tsconfig,
|
|
125
126
|
});
|
|
127
|
+
return CompilationSnapshot.run(options.env, (env) =>
|
|
128
|
+
transformWithSnapshot({ ...options, env }, loaded),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Run native checks and transformation after descriptor/source discovery. */
|
|
133
|
+
function transformWithSnapshot(
|
|
134
|
+
options: ITtscCompilerContext,
|
|
135
|
+
loaded: ReturnType<typeof loadProjectPlugins>,
|
|
136
|
+
): ReturnType<typeof transformProjectInMemory> {
|
|
126
137
|
if (loaded.nativePlugins.length !== 0) {
|
|
127
138
|
// Every step below runs binaries built from these sources, so the output
|
|
128
139
|
// is a function of their state, which the envelope carries for a consumer
|
|
@@ -393,7 +404,11 @@ function transformProjectWithPlugins(
|
|
|
393
404
|
);
|
|
394
405
|
return {
|
|
395
406
|
...envelopeSideChannels(transformed),
|
|
396
|
-
graph:
|
|
407
|
+
graph: mergeTransformGraphs(
|
|
408
|
+
checked.graph,
|
|
409
|
+
transformed.graph,
|
|
410
|
+
checks.length,
|
|
411
|
+
),
|
|
397
412
|
...observationLimitations(
|
|
398
413
|
[loaded, checked, transformed],
|
|
399
414
|
hostInputHashes,
|
|
@@ -476,7 +491,7 @@ function transformProjectWithPlugins(
|
|
|
476
491
|
);
|
|
477
492
|
return {
|
|
478
493
|
...envelopeSideChannels(output),
|
|
479
|
-
graph:
|
|
494
|
+
graph: mergeTransformGraphs(checked.graph, output.graph, checks.length),
|
|
480
495
|
...observationLimitations(
|
|
481
496
|
[loaded, checked, output],
|
|
482
497
|
hostInputHashes,
|
|
@@ -708,6 +723,7 @@ function runNativeChecks(
|
|
|
708
723
|
stdout: "",
|
|
709
724
|
stderr: "",
|
|
710
725
|
};
|
|
726
|
+
let snapshotsComplete = true;
|
|
711
727
|
for (const plugin of checks) {
|
|
712
728
|
const checked = runNativeCheckWithObservations(plugin, (extraArgs) => {
|
|
713
729
|
const res = spawnNative(
|
|
@@ -748,7 +764,12 @@ function runNativeChecks(
|
|
|
748
764
|
project.root,
|
|
749
765
|
);
|
|
750
766
|
});
|
|
767
|
+
snapshotsComplete &&= checked.graph?.compilationSnapshot !== undefined;
|
|
751
768
|
result = appendBuildOutput(result, checked);
|
|
769
|
+
if (!snapshotsComplete && result.graph?.compilationSnapshot !== undefined) {
|
|
770
|
+
const { compilationSnapshot: _snapshot, ...unqualified } = result.graph;
|
|
771
|
+
result.graph = unqualified;
|
|
772
|
+
}
|
|
752
773
|
if (result.status !== 0) {
|
|
753
774
|
return result;
|
|
754
775
|
}
|
|
@@ -877,3 +898,23 @@ function nativePluginEnv(
|
|
|
877
898
|
);
|
|
878
899
|
return env;
|
|
879
900
|
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* A legacy check with no graph cannot certify the transform's shared input
|
|
904
|
+
* view.
|
|
905
|
+
*/
|
|
906
|
+
function mergeTransformGraphs(
|
|
907
|
+
checked: TtscBuildResult["graph"],
|
|
908
|
+
transformed: TtscBuildResult["graph"],
|
|
909
|
+
checks: number,
|
|
910
|
+
): TtscBuildResult["graph"] {
|
|
911
|
+
const graph = mergeCheckGraphs(checked, transformed);
|
|
912
|
+
if (
|
|
913
|
+
graph === undefined ||
|
|
914
|
+
checks === 0 ||
|
|
915
|
+
checked?.compilationSnapshot !== undefined
|
|
916
|
+
)
|
|
917
|
+
return graph;
|
|
918
|
+
const { compilationSnapshot: _snapshot, ...unqualified } = graph;
|
|
919
|
+
return unqualified;
|
|
920
|
+
}
|
|
@@ -51,6 +51,12 @@ export namespace ITtscCompilerTransformation {
|
|
|
51
51
|
* @evidence contracts/portability.md#os-neutral-implementation Graph keys use the producer's cwd-relative slash spelling with absolute slash paths for outside-root or cross-volume inputs; this protocol spelling is distinct from reported native realpaths carried by inputRealpaths and observations, which can retain lexical fallback on native VFS errors. The reported compiler case policy governs compiler membership comparisons rather than an OS-name assumption; absent policy or proof does not establish native filesystem capabilities.
|
|
52
52
|
*/
|
|
53
53
|
export interface IReferenceGraph {
|
|
54
|
+
/**
|
|
55
|
+
* Shared native read-once input view; current delivery authority, never
|
|
56
|
+
* disk freshness.
|
|
57
|
+
*/
|
|
58
|
+
compilationSnapshot?: string;
|
|
59
|
+
|
|
54
60
|
/**
|
|
55
61
|
* Effective native possible-root suffixes and output-directory exclusions.
|
|
56
62
|
* A report that differs from a host's provisional policy requires a new
|