ttsc 0.32.1 → 0.33.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.
Files changed (111) hide show
  1. package/cmd/ttsc-cache/account.go +94 -0
  2. package/cmd/ttsc-cache/interruption_test.go +176 -0
  3. package/cmd/ttsc-cache/main.go +252 -0
  4. package/cmd/ttsc-cache/merge.go +324 -0
  5. package/cmd/ttsc-cache/merge_test.go +161 -0
  6. package/cmd/ttsc-cache/prepare.go +330 -0
  7. package/cmd/ttsc-cache/prepare_test.go +456 -0
  8. package/cmd/ttsc-cache/protocol_test.go +119 -0
  9. package/cmd/ttsc-cache/publisher_seal_test.go +135 -0
  10. package/cmd/ttsc-cache/seal.go +427 -0
  11. package/cmd/ttsc-cache/stock_go_failure_test.go +94 -0
  12. package/cmd/ttsc-cache/stock_go_test.go +163 -0
  13. package/cmd/ttsc-cache/store.go +249 -0
  14. package/cmd/ttsc-cache/store_test.go +104 -0
  15. package/cmd/ttsc-cache/tuple.go +107 -0
  16. package/cmd/ttsc-cache/tuple_test.go +62 -0
  17. package/cmd/ttsc-cache/usage.go +73 -0
  18. package/driver/configloaderscratch/canonical_directory_other.go +34 -0
  19. package/driver/configloaderscratch/canonical_directory_windows.go +60 -0
  20. package/driver/configloaderscratch/identity_temp_base.go +81 -0
  21. package/driver/configloaderscratch/identity_temp_base_posix_test.go +124 -0
  22. package/driver/configloaderscratch/identity_temp_base_windows_test.go +156 -0
  23. package/driver/emit_provenance.go +7 -5
  24. package/driver/resolutioninputs/recorder.cjs +16 -4
  25. package/driver/rewrite.go +25 -16
  26. package/driver/write_file_if_changed.go +69 -0
  27. package/driver/write_file_if_changed_test.go +148 -0
  28. package/internal/packageartifacts/manifest.go +288 -0
  29. package/internal/packageartifacts/manifest_test.go +127 -0
  30. package/lib/compiler/internal/build/BuildExecution.d.ts +11 -7
  31. package/lib/compiler/internal/build/BuildExecution.js +22 -8
  32. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  33. package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +10 -10
  34. package/lib/compiler/internal/build/ResidentCheckWatchSession.js +19 -14
  35. package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
  36. package/lib/compiler/internal/build/RunBuildOptions.d.ts +8 -1
  37. package/lib/compiler/internal/build/TsgoArguments.d.ts +16 -0
  38. package/lib/compiler/internal/build/TsgoArguments.js +19 -0
  39. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  40. package/lib/compiler/internal/captureProcessOutput.d.ts +16 -13
  41. package/lib/compiler/internal/captureProcessOutput.js +22 -14
  42. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  43. package/lib/compiler/internal/startResidentTransform.js +3 -1
  44. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  45. package/lib/compiler/internal/transformProjectInMemory.js +5 -1
  46. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  47. package/lib/launcher/internal/runtime/PluginDescriptorInputReport.d.ts +43 -0
  48. package/lib/launcher/internal/runtime/PluginDescriptorInputReport.js +106 -0
  49. package/lib/launcher/internal/runtime/PluginDescriptorInputReport.js.map +1 -0
  50. package/lib/launcher/internal/runtime/installRuntimeHooks.js +15 -29
  51. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  52. package/lib/launcher/internal/ttscserver/runTtscserver.js +1 -0
  53. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  54. package/lib/launcher/internal/watch/WatchTopology.js +5 -4
  55. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  56. package/lib/plugin/internal/CapabilityResolutionFormat.js +4 -3
  57. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  58. package/lib/plugin/internal/load/COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE.js +6 -0
  59. package/lib/plugin/internal/load/COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE.js.map +1 -1
  60. package/lib/plugin/internal/load/PLUGIN_DESCRIPTOR_SHIM_SOURCE.js +6 -0
  61. package/lib/plugin/internal/load/PLUGIN_DESCRIPTOR_SHIM_SOURCE.js.map +1 -1
  62. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +3 -3
  63. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  64. package/lib/plugin/internal/load/descriptorRecordEnvironment.d.ts +15 -0
  65. package/lib/plugin/internal/load/descriptorRecordEnvironment.js +24 -0
  66. package/lib/plugin/internal/load/descriptorRecordEnvironment.js.map +1 -0
  67. package/lib/plugin/internal/load/loadProjectPlugins.js +28 -26
  68. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  69. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +2 -0
  70. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +2 -0
  71. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  72. package/lib/plugin/internal/source/SourcePackageArtifacts.d.ts +145 -0
  73. package/lib/plugin/internal/source/SourcePackageArtifacts.js +612 -0
  74. package/lib/plugin/internal/source/SourcePackageArtifacts.js.map +1 -0
  75. package/lib/plugin/internal/source/buildSourcePlugin.js +54 -4
  76. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  77. package/lib/plugin/internal/source/prunePackageArtifactRoot.d.ts +19 -0
  78. package/lib/plugin/internal/source/prunePackageArtifactRoot.js +95 -0
  79. package/lib/plugin/internal/source/prunePackageArtifactRoot.js.map +1 -0
  80. package/lib/plugin/internal/source/resolveCleanTargets.js +1 -1
  81. package/lib/plugin/internal/source/resolveCleanTargets.js.map +1 -1
  82. package/lib/plugin/internal/source/resolveSourceBuildCachePaths.js +9 -4
  83. package/lib/plugin/internal/source/resolveSourceBuildCachePaths.js.map +1 -1
  84. package/lib/plugin/internal/source/selectedRecordIdentityTempParent.d.ts +18 -0
  85. package/lib/plugin/internal/source/selectedRecordIdentityTempParent.js +46 -0
  86. package/lib/plugin/internal/source/selectedRecordIdentityTempParent.js.map +1 -0
  87. package/package.json +8 -8
  88. package/src/compiler/internal/build/BuildExecution.ts +30 -10
  89. package/src/compiler/internal/build/ResidentCheckWatchSession.ts +23 -22
  90. package/src/compiler/internal/build/RunBuildOptions.ts +9 -1
  91. package/src/compiler/internal/build/TsgoArguments.ts +22 -0
  92. package/src/compiler/internal/captureProcessOutput.ts +28 -14
  93. package/src/compiler/internal/startResidentTransform.ts +7 -1
  94. package/src/compiler/internal/transformProjectInMemory.ts +9 -1
  95. package/src/launcher/internal/runtime/PluginDescriptorInputReport.ts +100 -0
  96. package/src/launcher/internal/runtime/installRuntimeHooks.ts +57 -55
  97. package/src/launcher/internal/ttscserver/runTtscserver.ts +5 -0
  98. package/src/launcher/internal/watch/WatchTopology.ts +5 -4
  99. package/src/plugin/internal/CapabilityResolutionFormat.ts +4 -3
  100. package/src/plugin/internal/load/COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE.ts +4 -0
  101. package/src/plugin/internal/load/PLUGIN_DESCRIPTOR_SHIM_SOURCE.ts +4 -0
  102. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +3 -3
  103. package/src/plugin/internal/load/descriptorRecordEnvironment.ts +24 -0
  104. package/src/plugin/internal/load/loadProjectPlugins.ts +45 -29
  105. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +3 -0
  106. package/src/plugin/internal/source/SourcePackageArtifacts.ts +787 -0
  107. package/src/plugin/internal/source/buildSourcePlugin.ts +82 -12
  108. package/src/plugin/internal/source/prunePackageArtifactRoot.ts +100 -0
  109. package/src/plugin/internal/source/resolveCleanTargets.ts +1 -0
  110. package/src/plugin/internal/source/resolveSourceBuildCachePaths.ts +11 -4
  111. package/src/plugin/internal/source/selectedRecordIdentityTempParent.ts +40 -0
@@ -32,6 +32,7 @@ import { DependencyBuildGeneration } from "./DependencyBuildGeneration";
32
32
  import { OwnedProjectSource } from "./OwnedProjectSource";
33
33
  import type { OwningModuleOptions } from "./OwningModuleOptions";
34
34
  import { PluginDescriptorInputObservation } from "./PluginDescriptorInputObservation";
35
+ import { PluginDescriptorInputReport } from "./PluginDescriptorInputReport";
35
36
  import type { ResolveResult } from "./ResolveResult";
36
37
  import { RuntimeEmitProvenance } from "./RuntimeEmitProvenance";
37
38
  import { RuntimeEntrySources } from "./RuntimeEntrySources";
@@ -392,7 +393,7 @@ function observePluginDescriptorResolutionCandidates(
392
393
  );
393
394
  return {
394
395
  commit: (selectedURL) => {
395
- appendPluginDescriptorInputs(
396
+ PluginDescriptorInputReport.append(
396
397
  observation
397
398
  .commit(
398
399
  selectedURL === undefined
@@ -431,7 +432,7 @@ function observePluginDescriptorResolutionCandidates(
431
432
  DESCRIPTOR_PROBE_EXTENSIONS,
432
433
  ),
433
434
  );
434
- appendPluginDescriptorInputs([
435
+ PluginDescriptorInputReport.append([
435
436
  ...local,
436
437
  ...(reached === -1 ? roots : roots.slice(0, reached + 1)).flatMap(
437
438
  (root) => root.lines,
@@ -567,7 +568,8 @@ function observePluginDescriptorResolutionCandidates(
567
568
  * active for direct-evaluator user preloads after hook installation. The exact
568
569
  * generated evaluator entry bypasses these hooks and suspends recording only
569
570
  * while its synchronous implementation imports load; descriptor imports then
570
- * resume it.
571
+ * resume it. A selected file needs positive content and physical proof even
572
+ * when an earlier candidate enumeration did not name it.
571
573
  */
572
574
  function recordPluginDescriptorResolution(
573
575
  specifier: string,
@@ -578,11 +580,14 @@ function recordPluginDescriptorResolution(
578
580
  const resolved = runtimeFilePath(resolvedURL);
579
581
  if (resolved === undefined) return;
580
582
  const parent = runtimeFilePath(parentURL);
581
- recordPluginDescriptorInput({
582
- ...(parent === undefined ? {} : { parent }),
583
- resolved,
584
- specifier,
585
- });
583
+ recordPluginDescriptorInput(
584
+ {
585
+ ...(parent === undefined ? {} : { parent }),
586
+ resolved,
587
+ specifier,
588
+ },
589
+ true,
590
+ );
586
591
  for (
587
592
  let directory = path.dirname(resolved);
588
593
  ;
@@ -599,38 +604,47 @@ function recordPluginDescriptorResolution(
599
604
  }
600
605
  }
601
606
 
602
- function recordPluginDescriptorInput(record: {
603
- hash?: string | null;
604
- parent?: string;
605
- realpath?: string | null;
606
- resolved: string;
607
- signature?: string;
608
- specifier?: string;
609
- unstable?: boolean;
610
- }): void {
611
- appendPluginDescriptorInputs(observePluginDescriptorInput(record));
607
+ function recordPluginDescriptorInput(
608
+ record: {
609
+ hash?: string | null;
610
+ parent?: string;
611
+ realpath?: string | null;
612
+ resolved: string;
613
+ signature?: string;
614
+ specifier?: string;
615
+ unstable?: boolean;
616
+ },
617
+ requirePresent: boolean = false,
618
+ ): void {
619
+ PluginDescriptorInputReport.append(
620
+ observePluginDescriptorInput(record, requirePresent),
621
+ );
612
622
  }
613
623
 
614
624
  /**
615
625
  * Observe one path and every symbolic link among its lexical ancestors, as the
616
- * lines `appendPluginDescriptorInputs` reports, without reporting them yet.
626
+ * lines `PluginDescriptorInputReport.append` reports, without reporting them
627
+ * yet.
617
628
  */
618
- function observePluginDescriptorInput(record: {
619
- hash?: string | null;
620
- parent?: string;
621
- realpath?: string | null;
622
- resolved: string;
623
- signature?: string;
624
- specifier?: string;
625
- unstable?: boolean;
626
- }): string[] {
629
+ function observePluginDescriptorInput(
630
+ record: {
631
+ hash?: string | null;
632
+ parent?: string;
633
+ realpath?: string | null;
634
+ resolved: string;
635
+ signature?: string;
636
+ specifier?: string;
637
+ unstable?: boolean;
638
+ },
639
+ requirePresent: boolean = false,
640
+ ): string[] {
627
641
  if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1") return [];
628
642
  const lines: string[] = [];
629
643
  const observe = (line: string | undefined): void => {
630
644
  if (line !== undefined) lines.push(line);
631
645
  else PluginDescriptorInputObservation.invalidate();
632
646
  };
633
- observe(observePluginDescriptorInputOnce(record));
647
+ observe(observePluginDescriptorInputOnce(record, requirePresent));
634
648
  const resolved = path.resolve(record.resolved);
635
649
  const parsed = path.parse(resolved);
636
650
  let current = parsed.root;
@@ -649,33 +663,19 @@ function observePluginDescriptorInput(record: {
649
663
  return lines;
650
664
  }
651
665
 
652
- /** Report observed lines to the parent loader, in the order given. */
653
- function appendPluginDescriptorInputs(lines: readonly string[]): void {
654
- if (lines.length === 0) return;
655
- if (process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE !== "1") return;
656
- PluginDescriptorInputObservation.record(lines);
657
- const out = process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT;
658
- if (out === undefined || out.length === 0) {
659
- PluginDescriptorInputObservation.invalidate();
660
- return;
661
- }
662
- try {
663
- fs.appendFileSync(out, lines.join(""), "utf8");
664
- } catch {
665
- PluginDescriptorInputObservation.invalidate();
666
- }
667
- }
668
-
669
666
  /** Observe one path without recursively revisiting its lexical ancestors. */
670
- function observePluginDescriptorInputOnce(record: {
671
- hash?: string | null;
672
- parent?: string;
673
- realpath?: string | null;
674
- resolved: string;
675
- signature?: string;
676
- specifier?: string;
677
- unstable?: boolean;
678
- }): string | undefined {
667
+ function observePluginDescriptorInputOnce(
668
+ record: {
669
+ hash?: string | null;
670
+ parent?: string;
671
+ realpath?: string | null;
672
+ resolved: string;
673
+ signature?: string;
674
+ specifier?: string;
675
+ unstable?: boolean;
676
+ },
677
+ requirePresent: boolean = false,
678
+ ): string | undefined {
679
679
  try {
680
680
  const beforeSignature = pluginDescriptorInputMetadataSignature(
681
681
  record.resolved,
@@ -687,6 +687,8 @@ function observePluginDescriptorInputOnce(record: {
687
687
  );
688
688
  const unstable =
689
689
  record.unstable === true ||
690
+ (requirePresent &&
691
+ (observedHash === null || observedRealpath === null)) ||
690
692
  beforeSignature === undefined ||
691
693
  afterSignature === undefined ||
692
694
  beforeSignature !== afterSignature ||
@@ -265,6 +265,11 @@ function lspSidecarEnvironment(options: {
265
265
  });
266
266
  const node = resolveNodeBinary(env, options.cwd, options.identities);
267
267
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
268
+ SidecarEnvironment.write(
269
+ env,
270
+ "TTSC_NATIVE_PLUGIN_RECORD_ROOT",
271
+ options.identities?.root,
272
+ );
268
273
  SidecarEnvironment.write(
269
274
  env,
270
275
  "TTSC_PLUGIN_CONFIG_DIR",
@@ -2740,17 +2740,18 @@ function projectInputDeclarationKey(
2740
2740
  *
2741
2741
  * Modification time and size avoid a read on an ordinary broad scan. A strong
2742
2742
  * event compares the fingerprint, so a timestamp collision cannot hide an edit
2743
- * and a metadata-only notification does not create a build. Device and inode
2744
- * answer whether a POSIX per-file watcher still owns the path.
2743
+ * and a metadata-only notification does not create a build. Exact device and
2744
+ * inode values detect replacement even when Windows file IDs exceed the safe
2745
+ * integer range; they also identify a POSIX per-file watcher's owner.
2745
2746
  */
2746
2747
  function compilerFileSnapshot(
2747
2748
  location: string,
2748
2749
  fingerprint: string,
2749
2750
  ): CompilerFileSnapshot {
2750
2751
  try {
2751
- const stats = fs.statSync(location);
2752
+ const stats = fs.statSync(location, { bigint: true });
2752
2753
  return {
2753
- content: `${stats.mtimeMs}:${stats.size}`,
2754
+ content: `${stats.mtimeNs}:${stats.size}`,
2754
2755
  fingerprint,
2755
2756
  owner: `${stats.dev}:${stats.ino}`,
2756
2757
  };
@@ -40,10 +40,11 @@ export namespace CapabilityResolutionFormat {
40
40
  * runtime observation envelope, so a partial side channel cannot authorize an
41
41
  * answer for reuse, and the shared module recorder's independent completion
42
42
  * proof, including its public require.resolve hook capability. Mapped import
43
- * candidates retain their pre-resolution witnesses; entries from before that
44
- * producer rule must be discovered again even when their later hashes match.
43
+ * candidates retain their pre-resolution witnesses. A selected file without
44
+ * positive content and physical proof now withdraws reuse, so entries from
45
+ * before that producer rule must be discovered again.
45
46
  */
46
- const FORMAT = "ttsc-capability-resolution-v7";
47
+ const FORMAT = "ttsc-capability-resolution-v8";
47
48
 
48
49
  /**
49
50
  * The character that joins fields a path could otherwise forge.
@@ -1,3 +1,5 @@
1
+ import path from "node:path";
2
+
1
3
  import {
2
4
  PLUGIN_INPUT_OBSERVATION_PATH,
3
5
  RESOLUTION_INPUT_RECORDER_PATH,
@@ -24,6 +26,7 @@ export const COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
24
26
  `const path = require("node:path");`,
25
27
  `const vm = require("node:vm");`,
26
28
  `const { PluginDescriptorInputObservation } = require.cache[${JSON.stringify(PLUGIN_INPUT_OBSERVATION_PATH)}]?.exports ?? require(${JSON.stringify(PLUGIN_INPUT_OBSERVATION_PATH)});`,
29
+ `const { PluginDescriptorInputReport } = require(${JSON.stringify(path.resolve(path.dirname(PLUGIN_INPUT_OBSERVATION_PATH), "PluginDescriptorInputReport.js"))});`,
27
30
  `const { fileURLToPath } = require("node:url");`,
28
31
  `const { createResolutionInputRecorder, requireResolveConsultsHooks } = (() => {`,
29
32
  ` const file = ${JSON.stringify(RESOLUTION_INPUT_RECORDER_PATH)};`,
@@ -164,6 +167,7 @@ export const COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
164
167
  // that influence.
165
168
  ` const serializedDescriptor = JSON.stringify(descriptor);`,
166
169
  ` const recorded = recorder.finish();`,
170
+ ` PluginDescriptorInputReport.finish();`,
167
171
  ` const observation = PluginDescriptorInputObservation.snapshot();`,
168
172
  ` observation.complete = observation.complete && recorded.complete === true;`,
169
173
  ` const payload = { inputHashes: recorded.hashes, inputRealpaths: recorded.realpaths, inputs: recorded.inputs, observation };`,
@@ -1,3 +1,5 @@
1
+ import path from "node:path";
2
+
1
3
  import { PLUGIN_INPUT_OBSERVATION_PATH } from "./RESOLUTION_INPUT_RECORDER_PATH";
2
4
 
3
5
  /**
@@ -22,6 +24,7 @@ export const PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
22
24
  // factory could have said.
23
25
  `try {`,
24
26
  ` const { PluginDescriptorInputObservation } = createRequire(import.meta.url).cache[${JSON.stringify(PLUGIN_INPUT_OBSERVATION_PATH)}].exports;`,
27
+ ` const { PluginDescriptorInputReport } = createRequire(import.meta.url)(${JSON.stringify(path.resolve(path.dirname(PLUGIN_INPUT_OBSERVATION_PATH), "PluginDescriptorInputReport.js"))});`,
25
28
  // Runtime hooks are installed before this shim loads. Arm their internal
26
29
  // side channel only for the descriptor import itself, after this shim's own
27
30
  // imports have resolved, so ttsc implementation files never become project
@@ -38,6 +41,7 @@ export const PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
38
41
  ` throw new Error("ttsc: plugin descriptor declares unsupported JS transform functions; declare a native backend instead");`,
39
42
  ` }`,
40
43
  ` const serializedDescriptor = JSON.stringify(descriptor);`,
44
+ ` PluginDescriptorInputReport.finish();`,
41
45
  ` const payload = { observation: PluginDescriptorInputObservation.snapshot() };`,
42
46
  ` if (serializedDescriptor !== undefined) payload.descriptor = JSON.parse(serializedDescriptor);`,
43
47
  ` writeFileSync(process.env.TTSC_PLUGIN_DESCRIPTOR_OUT, JSON.stringify(payload));`,
@@ -352,9 +352,9 @@ export namespace PluginDescriptorEvaluationCache {
352
352
  * Entry format tag. Moves when the entry shape or its proof rule changes, so
353
353
  * an entry written under another rule is evaluated again.
354
354
  */
355
- // Earlier generations could replace mapped-resolution pre-witnesses with
356
- // later candidate state, so their apparently complete proofs are not reusable.
357
- const FORMAT = "ttsc-descriptor-evaluation-v7";
355
+ // Earlier generations could prove a selected file's disappearance as a
356
+ // missing candidate, so their apparently complete proofs are not reusable.
357
+ const FORMAT = "ttsc-descriptor-evaluation-v8";
358
358
 
359
359
  interface IEntry {
360
360
  evaluation: IEvaluation;
@@ -0,0 +1,24 @@
1
+ import { SidecarEnvironment } from "../../../compiler/internal/sharedHost/SidecarEnvironment";
2
+
3
+ /**
4
+ * Supplies a descriptor child with this load's selected record root. An absent
5
+ * store removes inherited spellings of the private channel, including Windows
6
+ * case aliases, so a nested child cannot adopt an earlier load's cache root.
7
+ *
8
+ * @evidence contracts/common.md#principled-implementation The selected store is the only authority for the child channel; SidecarEnvironment.write removes inherited aliases when it is absent.
9
+ * @evidence contracts/common.md#clear-and-simple-design One environment constructor serves direct and ttsx descriptor evaluators and their cache identity.
10
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The helper copies the caller's environment and changes only ttsc's private channel instead of mutating process.env or inventing a cache root.
11
+ * @evidence contracts/common.md#meaningful-documentation The comment identifies ownership of the channel and the nested-process failure prevented by clearing it.
12
+ * @evidence contracts/portability.md#os-neutral-implementation SidecarEnvironment applies Windows native case-insensitive identity while preserving POSIX variable spelling.
13
+ * @evidence contracts/performance.md#efficient-algorithms Copying E environment entries and removing matching Windows aliases takes O(E) time and memory; descriptor setup already materializes an environment for each child.
14
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work The selected store and effective environment are invocation-specific, so no prior constructed child environment is reused.
15
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The returned environment lives only with one descriptor spawn or cache identity and retains no external resource.
16
+ */
17
+ export function descriptorRecordEnvironment(
18
+ env: NodeJS.ProcessEnv,
19
+ recordRoot: string | undefined,
20
+ ): NodeJS.ProcessEnv {
21
+ const child = { ...env };
22
+ SidecarEnvironment.write(child, "TTSC_PLUGIN_RECORD_ROOT", recordRoot);
23
+ return child;
24
+ }
@@ -35,6 +35,7 @@ import { pluginBuildVersions } from "../source/pluginBuildVersions";
35
35
  import { pluginModuleReplaceDirectories } from "../source/pluginModuleReplaceDirectories";
36
36
  import { pluginSourceState } from "../source/pluginSourceState";
37
37
  import { resolvePluginGoModule } from "../source/resolvePluginGoModule";
38
+ import { selectedRecordIdentityTempParent } from "../source/selectedRecordIdentityTempParent";
38
39
  import { COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE } from "./COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE";
39
40
  import { PLUGIN_DESCRIPTOR_SHIM_SOURCE } from "./PLUGIN_DESCRIPTOR_SHIM_SOURCE";
40
41
  import { PluginDescriptorAdmission } from "./PluginDescriptorAdmission";
@@ -44,6 +45,7 @@ import { ProjectPluginEntries } from "./ProjectPluginEntries";
44
45
  import { collectProjectHostInputs } from "./collectProjectHostInputs";
45
46
  import { composePluginSources } from "./composePluginSources";
46
47
  import { declaresHostInputReads } from "./declaresHostInputReads";
48
+ import { descriptorRecordEnvironment } from "./descriptorRecordEnvironment";
47
49
  import { hashHostInputPaths } from "./hashHostInputPaths";
48
50
  import { moduleResolutionBaseSelects } from "./moduleResolutionBaseSelects";
49
51
  import { pluginLabel } from "./pluginLabel";
@@ -1547,9 +1549,11 @@ function loadCommonJsDescriptor(
1547
1549
  cwd: descriptorCache.projectRoot,
1548
1550
  // Everything the child receives besides the per-evaluation output paths.
1549
1551
  env: {
1550
- ...effectiveEnv,
1552
+ ...descriptorRecordEnvironment(
1553
+ effectiveEnv,
1554
+ descriptorCache.identities?.root,
1555
+ ),
1551
1556
  ...(node === undefined ? {} : { TTSC_NODE_BINARY: node }),
1552
- ...descriptorRecordRoot(descriptorCache.identities),
1553
1557
  TTSC_TTSX_BINARY: ttsx,
1554
1558
  },
1555
1559
  projectRoot: descriptorCache.projectRoot,
@@ -1579,7 +1583,7 @@ function loadCommonJsDescriptor(
1579
1583
  PluginDescriptorEvaluationCache.locate(cacheAuthority) === cacheFile
1580
1584
  )
1581
1585
  return cached;
1582
- const dir = createEvaluationTempDir();
1586
+ const dir = createEvaluationTempDir(descriptorCache.identities);
1583
1587
  const out = path.join(dir, "descriptor.json");
1584
1588
  const inputsOut = path.join(dir, "descriptor-inputs.ndjson");
1585
1589
  const diagnostics = path.join(dir, "descriptor.stderr");
@@ -1635,11 +1639,13 @@ function loadCommonJsDescriptor(
1635
1639
  {
1636
1640
  cwd: descriptorCache.projectRoot,
1637
1641
  env: {
1638
- ...effectiveEnv,
1642
+ ...descriptorRecordEnvironment(
1643
+ effectiveEnv,
1644
+ descriptorCache.identities?.root,
1645
+ ),
1639
1646
  // The direct evaluator may be Bun, but ttsx and native config
1640
1647
  // loaders require a real Node runtime with synchronous hooks.
1641
1648
  ...(node === undefined ? {} : { TTSC_NODE_BINARY: node }),
1642
- ...descriptorRecordRoot(descriptorCache.identities),
1643
1649
  TTSC_TTSX_BINARY: ttsx,
1644
1650
  TTSC_PLUGIN_CONTEXT: JSON.stringify(context),
1645
1651
  TTSC_PLUGIN_DESCRIPTOR_LOAD: "1",
@@ -2038,7 +2044,7 @@ function loadDescriptorViaTtsx(
2038
2044
  if (node === undefined || ttsx === undefined || ttsx.length === 0) {
2039
2045
  return undefined;
2040
2046
  }
2041
- const dir = createEvaluationTempDir();
2047
+ const dir = createEvaluationTempDir(descriptorCache.identities);
2042
2048
  const out = path.join(dir, "descriptor.json");
2043
2049
  const inputsOut = path.join(dir, "descriptor-inputs.ndjson");
2044
2050
  const shim = path.join(dir, "load-descriptor.mts");
@@ -2067,12 +2073,14 @@ function loadDescriptorViaTtsx(
2067
2073
  cwd: descriptorCache.projectRoot,
2068
2074
  encoding: "utf8",
2069
2075
  env: {
2070
- ...effectiveEnv,
2076
+ ...descriptorRecordEnvironment(
2077
+ effectiveEnv,
2078
+ descriptorCache.identities?.root,
2079
+ ),
2071
2080
  // Carry ttsc's own node/ttsx locators explicitly so the child (which
2072
2081
  // may recurse into further descriptor loads) finds them even when the
2073
2082
  // instance-env snapshot predates `withPluginLoaderEnv`.
2074
2083
  TTSC_NODE_BINARY: node,
2075
- ...descriptorRecordRoot(descriptorCache.identities),
2076
2084
  TTSC_TTSX_BINARY: ttsx,
2077
2085
  TTSC_PLUGIN_CONTEXT: JSON.stringify({
2078
2086
  binary: context.binary,
@@ -2157,20 +2165,6 @@ interface TtsxDescriptorResolutionRecord {
2157
2165
  unstable?: boolean;
2158
2166
  }
2159
2167
 
2160
- /**
2161
- * The private variable that hands a descriptor evaluator the plugin cache root
2162
- * this load's record store selected, so the child proves the compiler it runs
2163
- * from that root's records instead of streaming it (#1726) and never selects a
2164
- * cache root of its own. Absent without a store.
2165
- */
2166
- function descriptorRecordRoot(
2167
- identities: PluginContentIdentities.Store | undefined,
2168
- ): { TTSC_PLUGIN_RECORD_ROOT?: string } {
2169
- return identities === undefined
2170
- ? {}
2171
- : { TTSC_PLUGIN_RECORD_ROOT: identities.root };
2172
- }
2173
-
2174
2168
  /**
2175
2169
  * Expand the ttsx runtime's selected module edges into the same exact and
2176
2170
  * missing resolution inputs used by the direct isolated evaluator.
@@ -2197,22 +2191,38 @@ function readTtsxDescriptorInputs(
2197
2191
  const signatures = new Map<string, string>();
2198
2192
  const unstableInputs = new Set<string>();
2199
2193
  let complete = false;
2200
- let text = "";
2194
+ let envelopeText: string | undefined;
2201
2195
  if (
2202
2196
  PluginPackageResolution.isRecord(observation) &&
2203
2197
  Array.isArray(observation.lines) &&
2204
2198
  observation.lines.every((line: unknown) => typeof line === "string")
2205
2199
  ) {
2206
2200
  complete = observation.complete === true;
2207
- text = observation.lines.join("");
2201
+ envelopeText = observation.lines.join("");
2208
2202
  }
2203
+ let reportText: string | undefined;
2209
2204
  try {
2210
- text += fs.readFileSync(file, "utf8");
2205
+ reportText = fs.readFileSync(file, "utf8");
2211
2206
  } catch {
2212
2207
  // The directly serialized observation copy remains authoritative when
2213
2208
  // no side-channel file was produced. A missing completion copy is never
2214
2209
  // repaired by whichever partial lines happen to remain on disk.
2215
2210
  }
2211
+ if (
2212
+ envelopeText !== undefined &&
2213
+ reportText !== undefined &&
2214
+ envelopeText !== reportText
2215
+ ) {
2216
+ // A partial or contradictory side channel cannot certify completeness.
2217
+ // Keep both records so watch callers still learn every reported input.
2218
+ complete = false;
2219
+ }
2220
+ const text =
2221
+ envelopeText === undefined
2222
+ ? (reportText ?? "")
2223
+ : reportText === undefined || reportText === envelopeText
2224
+ ? envelopeText
2225
+ : `${envelopeText}\n${reportText}`;
2216
2226
  for (const line of text.split(/\r?\n/)) {
2217
2227
  if (line.trim() === "") continue;
2218
2228
  let record: TtsxDescriptorResolutionRecord;
@@ -2516,11 +2526,17 @@ function ttscPackageRoot(): string {
2516
2526
  }
2517
2527
 
2518
2528
  /**
2519
- * Create an evaluator directory whose cleanup path cannot follow a retargeted
2520
- * parent alias.
2529
+ * Create an evaluator directory under the current selected record store when it
2530
+ * has an ordinary identities child. Canonical allocation fixes the physical
2531
+ * cleanup path; an absent or unproved store retains system TEMP.
2521
2532
  */
2522
- function createEvaluationTempDir(): string {
2523
- return createCanonicalTempDirectory("ttsc-plugin-descriptor-");
2533
+ function createEvaluationTempDir(
2534
+ identities: PluginContentIdentities.Store | undefined,
2535
+ ): string {
2536
+ return createCanonicalTempDirectory(
2537
+ "ttsc-plugin-descriptor-",
2538
+ selectedRecordIdentityTempParent(identities?.root),
2539
+ );
2524
2540
  }
2525
2541
 
2526
2542
  /**
@@ -44,6 +44,9 @@ export namespace SourceBuildCacheLayout {
44
44
  /** Directory name of ttsc's own Go object cache inside the cache root. */
45
45
  export const GO_BUILD_CACHE_DIRNAME = "go-build";
46
46
 
47
+ /** Directory of publisher package bodies and local Go cache writes. */
48
+ export const PACKAGE_ARTIFACTS_DIRNAME = "package-artifacts";
49
+
47
50
  /** File inside a plugin cache entry recording when it was last used. */
48
51
  export const CACHE_LAST_USED_FILE = ".last-used";
49
52