ttsc 0.31.1 → 0.31.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/compiler/internal/build/BuildExecution.d.ts +4 -2
- package/lib/compiler/internal/build/BuildExecution.js +40 -12
- package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +5 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +9 -7
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
- package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
- package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
- package/lib/internal/pluginSource.d.ts +1 -0
- package/lib/internal/pluginSource.js +1 -0
- package/lib/internal/pluginSource.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +6 -1
- package/lib/internal/resolveNodeBinary.js +6 -2
- package/lib/internal/resolveNodeBinary.js.map +1 -1
- package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
- package/lib/internal/runtimeExecutableIdentity.js +192 -102
- package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
- package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
- package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
- package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
- package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
- package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
- package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
- package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
- package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
- package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -1
- package/lib/plugin/internal/load/loadProjectPlugins.js +80 -20
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
- package/lib/plugin/internal/readCapabilityResolution.js +8 -5
- package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +9 -3
- package/lib/plugin/internal/source/GoEnvironmentReading.js +53 -7
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -1
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +44 -6
- package/lib/plugin/internal/source/NativeSourcePackages.js +150 -8
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +32 -7
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
- package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
- package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
- package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
- package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
- package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +5 -2
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +5 -3
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +15 -2
- package/lib/plugin/internal/source/buildSourcePlugin.js +27 -4
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +21 -5
- package/lib/plugin/internal/source/computeCacheKey.js +71 -29
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +74 -35
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
- package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
- package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
- package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
- package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
- package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
- package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
- package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
- package/lib/plugin/internal/source/pluginSourceState.js +20 -8
- package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
- package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
- package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
- package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
- package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
- package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
- package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
- package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
- package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
- package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
- package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
- package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
- package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
- package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
- package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
- package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
- package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
- package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
- package/lib/plugin/resolveCapabilityPlugins.js +67 -15
- package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
- package/package.json +8 -8
- package/src/compiler/internal/build/BuildExecution.ts +46 -13
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +10 -2
- package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
- package/src/internal/pluginSource.ts +1 -0
- package/src/internal/resolveNodeBinary.ts +12 -1
- package/src/internal/runtimeExecutableIdentity.ts +227 -141
- package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
- package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
- package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
- package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
- package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
- package/src/plugin/internal/load/loadProjectPlugins.ts +212 -94
- package/src/plugin/internal/readCapabilityResolution.ts +17 -6
- package/src/plugin/internal/source/GoEnvironmentReading.ts +74 -5
- package/src/plugin/internal/source/NativeSourcePackages.ts +262 -5
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +41 -5
- package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
- package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
- package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +6 -1
- package/src/plugin/internal/source/buildSourcePlugin.ts +43 -3
- package/src/plugin/internal/source/computeCacheKey.ts +103 -47
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +144 -29
- package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
- package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
- package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
- package/src/plugin/internal/source/pluginSourceState.ts +28 -12
- package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
- package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
- package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
- package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
- package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
- package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
- package/src/plugin/resolveCapabilityPlugins.ts +87 -13
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { E2ETrace } from "../../../internal/E2ETrace";
|
|
6
|
+
import type { PluginContentIdentities } from "./PluginContentIdentities";
|
|
7
|
+
import { SourceBuildCacheLayout } from "./SourceBuildCacheLayout";
|
|
8
|
+
import { recordCacheFileUse } from "./recordCacheFileUse";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Answers a plugin load computed from inputs it could identify by content, kept
|
|
12
|
+
* in the plugin cache root under that identity so a later process with equal
|
|
13
|
+
* inputs reads the answer instead of computing it again.
|
|
14
|
+
*
|
|
15
|
+
* WARNING (#1721, #1723): the key is the whole proof. A caller must fold into
|
|
16
|
+
* `identity` every input that can change the answer, as content identities
|
|
17
|
+
* (`PluginContentIdentities`) or the authoritative live probe results, never a
|
|
18
|
+
* path, a timestamp or a value it merely expects to be stable. An answer is
|
|
19
|
+
* written only after the computation succeeded on inputs the caller proved
|
|
20
|
+
* unchanged around it; a failed or unproved computation is never recorded,
|
|
21
|
+
* since a later process could not tell it from a current one.
|
|
22
|
+
*
|
|
23
|
+
* Entries are single files published by rename, aged out by the single-file
|
|
24
|
+
* collector and removed by `ttsc clean`. Their identity includes the format tag
|
|
25
|
+
* and the ttsc version, so another release never reads them.
|
|
26
|
+
*
|
|
27
|
+
* @evidence contracts/common.md#principled-implementation The content-addressed key carries every caller-declared input identity; reads accept only a matching format, version and kind, and writes are refused for unproved computations by the caller contract.
|
|
28
|
+
* @evidence contracts/common.md#clear-and-simple-design One owner stores and loads opaque answers; callers own key composition, validation of the answer shape and the decision to record.
|
|
29
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts No answer is guessed; a missing, corrupt or foreign entry is a miss and the caller computes.
|
|
30
|
+
* @evidence contracts/common.md#meaningful-documentation Native paragraphs state the key contract that prevents stale answers, the recording rule and the storage lifetime.
|
|
31
|
+
* @evidence contracts/portability.md#os-neutral-implementation Native path joins and same-directory rename publication; keys are hashed text, independent of path dialect.
|
|
32
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms Members state their own costs.
|
|
33
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Members define reuse through the caller's identity.
|
|
34
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Members state retention; the namespace holds no state.
|
|
35
|
+
*/
|
|
36
|
+
export namespace PluginLoadAnswers {
|
|
37
|
+
/**
|
|
38
|
+
* The answer recorded for `identity`, or `undefined` when none is.
|
|
39
|
+
*
|
|
40
|
+
* @param store The opened store, or `undefined` for no persistence.
|
|
41
|
+
* @param kind Answer family; families never share entries.
|
|
42
|
+
* @param identity Every input identity the answer depends on.
|
|
43
|
+
* @evidence contracts/common.md#principled-implementation An entry is returned only for the same format, version, kind and identity hash; the caller validates the value's shape.
|
|
44
|
+
* @evidence contracts/common.md#clear-and-simple-design One lookup returns an opaque value or undefined.
|
|
45
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts Unreadable or foreign entries are misses.
|
|
46
|
+
* @evidence contracts/common.md#meaningful-documentation Parameters describe the identity contract.
|
|
47
|
+
* @evidence contracts/portability.md#os-neutral-implementation Native path join and file read.
|
|
48
|
+
* @evidence contracts/performance.md#efficient-algorithms Hashes the serialized identity and reads one small file; a hit refreshes its use time.
|
|
49
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Processes share an answer only for an identical serialized identity under the same version.
|
|
50
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources Nothing is retained in memory; the collector owns entry lifetime.
|
|
51
|
+
*/
|
|
52
|
+
export function read(
|
|
53
|
+
store: PluginContentIdentities.Store | undefined,
|
|
54
|
+
kind: string,
|
|
55
|
+
identity: unknown,
|
|
56
|
+
): unknown {
|
|
57
|
+
if (store === undefined) return undefined;
|
|
58
|
+
const file = answerFile(store, kind, identity);
|
|
59
|
+
try {
|
|
60
|
+
const entry = JSON.parse(
|
|
61
|
+
fs.readFileSync(file, "utf8"),
|
|
62
|
+
) as Partial<IEntry>;
|
|
63
|
+
if (
|
|
64
|
+
entry.format !== FORMAT ||
|
|
65
|
+
entry.version !== store.version ||
|
|
66
|
+
entry.kind !== kind ||
|
|
67
|
+
!Object.prototype.hasOwnProperty.call(entry, "value")
|
|
68
|
+
)
|
|
69
|
+
return undefined;
|
|
70
|
+
recordCacheFileUse(file);
|
|
71
|
+
E2ETrace.capabilityResolution("plugin-load-answer", {
|
|
72
|
+
kind,
|
|
73
|
+
outcome: "reused",
|
|
74
|
+
});
|
|
75
|
+
return entry.value;
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Record `value` as the answer for `identity`. Failures are ignored: the
|
|
83
|
+
* answer just computed stands, and the next process computes it again.
|
|
84
|
+
*
|
|
85
|
+
* @param store The opened store, or `undefined` for no persistence.
|
|
86
|
+
* @param kind Answer family.
|
|
87
|
+
* @param identity Every input identity the answer depends on.
|
|
88
|
+
* @param value JSON-serializable answer the caller proved for `identity`.
|
|
89
|
+
* @evidence contracts/common.md#principled-implementation Publication happens only when the caller decided the computation was proven; the entry records the same identity framing that read checks.
|
|
90
|
+
* @evidence contracts/common.md#clear-and-simple-design One atomic publication through the shared metadata writer.
|
|
91
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts A failed write never fails the load or fabricates an entry.
|
|
92
|
+
* @evidence contracts/common.md#meaningful-documentation Native prose states failure behavior.
|
|
93
|
+
* @evidence contracts/portability.md#os-neutral-implementation Same-directory staging and rename through the shared writer.
|
|
94
|
+
* @evidence contracts/performance.md#efficient-algorithms Serializes the identity and value once and writes one small file.
|
|
95
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work Publication creates the reusable entry; read owns reuse.
|
|
96
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources One file per identity, removed by the single-file collector or `ttsc clean`; staging cleanup belongs to the shared writer.
|
|
97
|
+
*/
|
|
98
|
+
export function write(
|
|
99
|
+
store: PluginContentIdentities.Store | undefined,
|
|
100
|
+
kind: string,
|
|
101
|
+
identity: unknown,
|
|
102
|
+
value: unknown,
|
|
103
|
+
): void {
|
|
104
|
+
if (store === undefined) return;
|
|
105
|
+
const file = answerFile(store, kind, identity);
|
|
106
|
+
try {
|
|
107
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
108
|
+
SourceBuildCacheLayout.replaceCacheMetadataFile(
|
|
109
|
+
file,
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
format: FORMAT,
|
|
112
|
+
version: store.version,
|
|
113
|
+
kind,
|
|
114
|
+
value,
|
|
115
|
+
} satisfies IEntry),
|
|
116
|
+
);
|
|
117
|
+
E2ETrace.capabilityResolution("plugin-load-answer", {
|
|
118
|
+
kind,
|
|
119
|
+
outcome: "recorded",
|
|
120
|
+
});
|
|
121
|
+
} catch {
|
|
122
|
+
// The answer just computed stands; only its reuse is lost.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Entry format tag; move it when the entry shape changes. */
|
|
127
|
+
const FORMAT = "ttsc-plugin-load-answer-v1";
|
|
128
|
+
|
|
129
|
+
interface IEntry {
|
|
130
|
+
format: string;
|
|
131
|
+
version: string;
|
|
132
|
+
kind: string;
|
|
133
|
+
value: unknown;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function answerFile(
|
|
137
|
+
store: PluginContentIdentities.Store,
|
|
138
|
+
kind: string,
|
|
139
|
+
identity: unknown,
|
|
140
|
+
): string {
|
|
141
|
+
const key = crypto
|
|
142
|
+
.createHash("sha256")
|
|
143
|
+
.update(JSON.stringify([FORMAT, store.version, kind, identity]))
|
|
144
|
+
.digest("hex");
|
|
145
|
+
return path.join(
|
|
146
|
+
store.root,
|
|
147
|
+
SourceBuildCacheLayout.ANSWER_CACHE_DIRNAME,
|
|
148
|
+
`${key}.json`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -53,6 +53,18 @@ export namespace SourceBuildCacheLayout {
|
|
|
53
53
|
/** Directory of the capability-resolution answers inside the cache root. */
|
|
54
54
|
export const CAPABILITY_CACHE_DIRNAME = "capabilities";
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Directory of the content identities a plugin load proves from metadata
|
|
58
|
+
* (`PluginContentIdentities`) inside the cache root.
|
|
59
|
+
*/
|
|
60
|
+
export const IDENTITY_CACHE_DIRNAME = "identities";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Directory of the plugin-load answers keyed by those identities, such as Go
|
|
64
|
+
* package selections and runtime capabilities (`PluginLoadAnswers`).
|
|
65
|
+
*/
|
|
66
|
+
export const ANSWER_CACHE_DIRNAME = "answers";
|
|
67
|
+
|
|
56
68
|
/** Directory of the ttsx runtime inside the cache root. */
|
|
57
69
|
export const RUNTIME_CACHE_DIRNAME = "ttsx";
|
|
58
70
|
|
|
@@ -72,6 +84,8 @@ export namespace SourceBuildCacheLayout {
|
|
|
72
84
|
export const CACHE_FILE_DIRNAMES: readonly string[] = [
|
|
73
85
|
DESCRIPTOR_CACHE_DIRNAME,
|
|
74
86
|
CAPABILITY_CACHE_DIRNAME,
|
|
87
|
+
IDENTITY_CACHE_DIRNAME,
|
|
88
|
+
ANSWER_CACHE_DIRNAME,
|
|
75
89
|
ORPHAN_CACHE_DIRNAME,
|
|
76
90
|
];
|
|
77
91
|
|
|
@@ -6,6 +6,7 @@ import { OwnedSynchronousProcess } from "../../../internal/OwnedSynchronousProce
|
|
|
6
6
|
import { GoEnvironmentReading } from "./GoEnvironmentReading";
|
|
7
7
|
import { GoSourceInputs } from "./GoSourceInputs";
|
|
8
8
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
9
|
+
import type { PluginContentIdentities } from "./PluginContentIdentities";
|
|
9
10
|
import { SourcePluginAdmission } from "./SourcePluginAdmission";
|
|
10
11
|
import { copiesPluginSourceEntry } from "./copiesPluginSourceEntry";
|
|
11
12
|
import { formatGoWorkPath } from "./formatGoWorkPath";
|
|
@@ -238,7 +239,9 @@ export namespace SourcePluginWorkspace {
|
|
|
238
239
|
* registry shares native reader state from the first watch query onward; its
|
|
239
240
|
* owner must keep one fixed effective environment and discard it on retry.
|
|
240
241
|
* Caller views share that native state and syntax, while each default
|
|
241
|
-
* diagnostic label remains with its current caller.
|
|
242
|
+
* diagnostic label remains with its current caller. An optional record store
|
|
243
|
+
* only carries the Go environment-file discovery hint across processes; it
|
|
244
|
+
* never carries a parse.
|
|
242
245
|
*
|
|
243
246
|
* @evidence contracts/common.md#principled-implementation Actual go mod edit JSON determines module/replacement identities; current bytes, selected/dispatched-tool metadata and Go's environment-file witness qualify reuse under the existing metadata-distinguishability premise. Environment values are copied at reader creation. Missing go.mod returns the existing empty module observation, and moving tool/manifest inputs cannot publish a parsed record.
|
|
244
247
|
* @evidence contracts/common.md#clear-and-simple-design One factory acquires load-owned native reader state by selected tool; a thin caller view forwards its diagnostic label without duplicating the byte-content map. Current reads and post-parse byte/tool comparisons qualify insertion.
|
|
@@ -254,6 +257,7 @@ export namespace SourcePluginWorkspace {
|
|
|
254
257
|
pluginName: string,
|
|
255
258
|
env: NodeJS.ProcessEnv,
|
|
256
259
|
readers?: Map<string, GoModReader>,
|
|
260
|
+
identities?: PluginContentIdentities.Store,
|
|
257
261
|
): GoModReader {
|
|
258
262
|
const existing = readers?.get(goBinary);
|
|
259
263
|
if (existing !== undefined)
|
|
@@ -315,6 +319,7 @@ export namespace SourcePluginWorkspace {
|
|
|
315
319
|
environment,
|
|
316
320
|
["GOROOT"],
|
|
317
321
|
witness,
|
|
322
|
+
identities,
|
|
318
323
|
)
|
|
319
324
|
: undefined;
|
|
320
325
|
if (
|
|
@@ -18,6 +18,7 @@ import { PluginBinaryUse } from "./PluginBinaryUse";
|
|
|
18
18
|
import { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
19
19
|
import type { PluginBuildLockLease } from "./PluginBuildLockLease";
|
|
20
20
|
import { PluginBuildLockProtocol } from "./PluginBuildLockProtocol";
|
|
21
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
21
22
|
import { SourceBuildCacheLayout } from "./SourceBuildCacheLayout";
|
|
22
23
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
23
24
|
import { SourcePluginAdmission } from "./SourcePluginAdmission";
|
|
@@ -88,8 +89,8 @@ import { withGoBuildCacheLease } from "./withGoBuildCacheLease";
|
|
|
88
89
|
* @evidence contracts/common.md#prohibited-implementation-shortcuts A changed witness is never overwritten or accepted. A fresh epoch takes a new toolchain reading and performs the actual build or qualified adoption; source, native and cleanup failures stay terminal. No selected SDK tool is warmed by a special command, and no foreign environment or memo is rebased. Shared Go objects retain their owner's toolID/actionID contract; same-version custom tool semantics beyond that contract are not newly guaranteed.
|
|
89
90
|
* @evidence contracts/common.md#meaningful-documentation Native paragraphs explain effective environment, recorded-input comparisons and their trust/observation limits, reader registration and managed versus explicit roots; option-map comments state their reading provenance with blank member separation.
|
|
90
91
|
* @evidence contracts/portability.md#os-neutral-implementation Node path/physical-cache/temp APIs preserve native identities; executable resolution and Windows command handling are isolated owners, and the binary filename explicitly follows its executable platform.
|
|
91
|
-
* @evidence contracts/performance.md#efficient-algorithms At most three epochs redo native toolchain observation, key construction, materialization and guarded admission;
|
|
92
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Existing binaries and concurrent builders share the version/platform/source/environment key with reader admission before return, assuming trustworthy cache producers and supplied digest maps. Fixed trimpath compilation removes disposable snapshot paths from Go object identities. Shared load readings and sequential source/toolchain comparisons reject observed changes; metadata reuse and unobserved concurrent mutation remain the underlying witnesses' limits.
|
|
92
|
+
* @evidence contracts/performance.md#efficient-algorithms At most three epochs redo native toolchain observation, key construction, materialization and guarded admission; the first can key on the load's witnessed same-directory reading instead of observing the toolchain again, and later ones always observe it. Each can run one Go build, preceded by one batched package metadata command when the loader requires ownership admission. The shared compiler reader can itself make three version observations, hence at most nine such cold observations for a persistently moving compiler and no build when its key witness fails. Full-file bytes, entries, path/sort text, contributors, external trees and witness populations drive work. Scoped cancellation is sampled between phases/entries/commands and does not bound native calls or delegated hashing. First source digest readings are reused across epochs, while each scratch is independently checked. Existing lock contention has its separate wait budget and native Go compiler work remains delegated.
|
|
93
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Existing binaries and concurrent builders share the version/platform/source/environment key with reader admission before return, assuming trustworthy cache producers and supplied digest maps. The key's content digests come from the record store when their separable metadata still matches, so a new process does not read unchanged sources, SDK or compiler bytes to find an existing binary (#1722). Fixed trimpath compilation removes disposable snapshot paths from Go object identities. Shared load readings and sequential source/toolchain comparisons reject observed changes; metadata reuse and unobserved concurrent mutation remain the underlying witnesses' limits.
|
|
93
94
|
* @evidence contracts/performance.md#bound-retention-and-release-resources Scratch directories and build/cache leases have finally-based cleanup, including cancellation; cancelled work admits no new post-build prune pass. Scratch removal or lease cleanup can fail, and selected synchronous child settlement does not join arbitrary descendants. Repeated commands can repeat external tool effects; only ttsc-owned failed outputs are discarded, and Go-owned object entries retain their existing lifetime. Pending binary cleanup is best-effort. Reader tokens and their process map grow with distinct physical keys until process exit. Managed pruning attempts age/LRU reclamation while protecting live, unknown and selected entries, so it is not a hard disk bound; explicit roots remain caller-managed.
|
|
94
95
|
*/
|
|
95
96
|
export function buildSourcePlugin(opts: {
|
|
@@ -113,7 +114,21 @@ export function buildSourcePlugin(opts: {
|
|
|
113
114
|
*/
|
|
114
115
|
environmentDigests?: Map<string, string>;
|
|
115
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Witnesses of the readings in `environmentDigests` (`computeCacheKey`), so
|
|
119
|
+
* the first epoch keys on a reading package selection already took. A
|
|
120
|
+
* discarded epoch reads the toolchain again instead.
|
|
121
|
+
*/
|
|
122
|
+
environmentWitnesses?: Map<string, PluginBuildEnvironmentWitness.Record>;
|
|
123
|
+
|
|
116
124
|
filesystem?: Partial<SourceBuildFilesystemOperations>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Record store the loader opened for this load, so its key reuses content
|
|
128
|
+
* identities across processes (#1722); a standalone build opens its own.
|
|
129
|
+
*/
|
|
130
|
+
identities?: PluginContentIdentities.Store;
|
|
131
|
+
|
|
117
132
|
label?: string;
|
|
118
133
|
overlayDirs?: readonly string[];
|
|
119
134
|
quiet?: boolean;
|
|
@@ -140,9 +155,17 @@ export function buildSourcePlugin(opts: {
|
|
|
140
155
|
for (let attempt = 0; ; attempt += 1) {
|
|
141
156
|
OwnedSynchronousProcess.checkpoint();
|
|
142
157
|
const environmentDigests = new Map(opts.environmentDigests);
|
|
158
|
+
// Only the first epoch may key on a reading taken before it; a discarded
|
|
159
|
+
// epoch means the toolchain moved, so later ones read it again.
|
|
160
|
+
const environmentWitnesses =
|
|
161
|
+
opts.environmentWitnesses === undefined
|
|
162
|
+
? undefined
|
|
163
|
+
: new Map(attempt === 0 ? opts.environmentWitnesses : []);
|
|
143
164
|
try {
|
|
144
165
|
const binary = buildSourcePluginAttempt(
|
|
145
|
-
attempt === 0
|
|
166
|
+
attempt === 0
|
|
167
|
+
? { ...opts, environmentWitnesses }
|
|
168
|
+
: { ...opts, environmentWitnesses, goModReaders: undefined },
|
|
146
169
|
env,
|
|
147
170
|
sourceDigests,
|
|
148
171
|
environmentDigests,
|
|
@@ -152,6 +175,8 @@ export function buildSourcePlugin(opts: {
|
|
|
152
175
|
opts.sourceDigests?.set(directory, digest);
|
|
153
176
|
for (const [directory, digest] of environmentDigests)
|
|
154
177
|
opts.environmentDigests?.set(directory, digest);
|
|
178
|
+
for (const [directory, witness] of environmentWitnesses ?? [])
|
|
179
|
+
opts.environmentWitnesses?.set(directory, witness);
|
|
155
180
|
return binary;
|
|
156
181
|
} catch (error) {
|
|
157
182
|
if (
|
|
@@ -192,6 +217,18 @@ function buildSourcePluginAttempt(
|
|
|
192
217
|
);
|
|
193
218
|
ensureExecutableGoToolchain(goBinary, compiler.bundled);
|
|
194
219
|
OwnedSynchronousProcess.checkpoint();
|
|
220
|
+
const identities =
|
|
221
|
+
opts.identities ??
|
|
222
|
+
PluginContentIdentities.open({
|
|
223
|
+
projectRoot: opts.baseDir,
|
|
224
|
+
cacheDir: opts.cacheDir,
|
|
225
|
+
env,
|
|
226
|
+
sources: [
|
|
227
|
+
dir,
|
|
228
|
+
...overlayDirs,
|
|
229
|
+
...contributors.map((contributor) => contributor.source),
|
|
230
|
+
],
|
|
231
|
+
});
|
|
195
232
|
// The digest of every directory the key covers, as the key read it, which
|
|
196
233
|
// the build proves against what it compiled.
|
|
197
234
|
const goModReader = SourcePluginWorkspace.createGoModReader(
|
|
@@ -199,6 +236,7 @@ function buildSourcePluginAttempt(
|
|
|
199
236
|
opts.pluginName,
|
|
200
237
|
env,
|
|
201
238
|
opts.goModReaders,
|
|
239
|
+
identities,
|
|
202
240
|
);
|
|
203
241
|
const environmentWitness: PluginBuildEnvironmentWitness.Record = new Map();
|
|
204
242
|
const key = computeCacheKey({
|
|
@@ -211,8 +249,10 @@ function buildSourcePluginAttempt(
|
|
|
211
249
|
goModReader,
|
|
212
250
|
filesystem: opts.filesystem,
|
|
213
251
|
goBinary,
|
|
252
|
+
identities,
|
|
214
253
|
overlayDirs,
|
|
215
254
|
environmentDigests,
|
|
255
|
+
environmentWitnesses: opts.environmentWitnesses,
|
|
216
256
|
sourceDigests,
|
|
217
257
|
ttscVersion: opts.ttscVersion,
|
|
218
258
|
tsgoVersion: opts.tsgoVersion,
|
|
@@ -5,11 +5,11 @@ import path from "node:path";
|
|
|
5
5
|
import { GoToolResolution } from "./GoToolResolution";
|
|
6
6
|
import type { ITtscBuildContributor } from "./ITtscBuildContributor";
|
|
7
7
|
import type { PluginBuildEnvironmentWitness } from "./PluginBuildEnvironmentWitness";
|
|
8
|
+
import { PluginContentIdentities } from "./PluginContentIdentities";
|
|
8
9
|
import type { SourceBuildFilesystemOperations } from "./SourceBuildFilesystemOperations";
|
|
9
10
|
import type { SourcePluginWorkspace } from "./SourcePluginWorkspace";
|
|
10
11
|
import { hashPluginBuildEnvironment } from "./hashPluginBuildEnvironment";
|
|
11
12
|
import { pluginModuleReplaceDirectories } from "./pluginModuleReplaceDirectories";
|
|
12
|
-
import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Compute a 32-character lowercase key from a plugin build's SHA-256 digest.
|
|
@@ -30,10 +30,12 @@ import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
|
30
30
|
* values are trusted producer readings, not revalidated content observations.
|
|
31
31
|
* `sourceDigests` carries the digests one load already took: every build of the
|
|
32
32
|
* load keys on one reading of each directory, and the load reports those
|
|
33
|
-
* readings. The environment enters
|
|
34
|
-
* rule that reported state takes it from too,
|
|
35
|
-
* the digest of each build directory's, so the
|
|
36
|
-
* `go env` run.
|
|
33
|
+
* readings. The environment enters as the digest of
|
|
34
|
+
* `hashPluginBuildEnvironment`, the rule that reported state takes it from too,
|
|
35
|
+
* and `environmentDigests` carries the digest of each build directory's, so the
|
|
36
|
+
* load reports it without a second `go env` run. With `environmentWitnesses`, a
|
|
37
|
+
* reading the load already took for the same directory, such as package
|
|
38
|
+
* selection's, keys the build too, and its witness joins the build's.
|
|
37
39
|
*
|
|
38
40
|
* The `ttsc cache` CLI and plugin build pipeline share this key computation.
|
|
39
41
|
*
|
|
@@ -43,7 +45,7 @@ import { pluginSourceDigest } from "./pluginSourceDigest";
|
|
|
43
45
|
* @evidence contracts/common.md#meaningful-documentation Native paragraphs explain input coverage and the provenance of reported digests; documented optional output maps have blank separation between members.
|
|
44
46
|
* @evidence contracts/portability.md#os-neutral-implementation Native roots are resolved with Node path APIs and executable resolution; platform/architecture intentionally distinguish incompatible binary artifacts.
|
|
45
47
|
* @evidence contracts/performance.md#efficient-algorithms Source digest misses enumerate/sort paths and read individual files in full, not bounded streaming chunks. Overlay/contributor ordering compares path/name text; replacement discovery can read a manifest/run Go and native containment queries. Toolchain/env hashing delegates native metadata/probes/full bytes and memo checks without an unmeasured dominant-cost ranking; JSON framing processes all version/entry/label/digest text.
|
|
46
|
-
* @evidence contracts/performance.md#reuse-equivalent-work Caller-owned sourceDigests share supplied absolute-directory readings across roles without independently verifying their provenance/currentness. EnvironmentDigests receives this call's selected environment identity so reporting can avoid another probe; it
|
|
48
|
+
* @evidence contracts/performance.md#reuse-equivalent-work Caller-owned sourceDigests share supplied absolute-directory readings across roles without independently verifying their provenance/currentness. A supplied record store lets a new process reuse a directory, SDK or executable digest only while its separable metadata signature matches the recorded one (#1722), so unchanged inputs are not re-read per process. EnvironmentDigests receives this call's selected environment identity so reporting can avoid another probe. A same-directory reading of the same load, with its witness, replaces a second environment probe; the witness then joins this build's, which the build compares after it ran, and a non-default byte adapter never shares. Reuse inherits the selected population and metadata/producer premises.
|
|
47
49
|
*
|
|
48
50
|
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Output maps belong to the enclosing load; this operation retains no handles or process-wide records itself.
|
|
49
51
|
*/
|
|
@@ -66,6 +68,14 @@ export function computeCacheKey(inputs: {
|
|
|
66
68
|
*/
|
|
67
69
|
environmentDigests?: Map<string, string>;
|
|
68
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Witness of each reading in `environmentDigests` this load took, by
|
|
73
|
+
* directory. A digest with its witness stands for this call's own reading:
|
|
74
|
+
* the key takes it instead of running Go again and copies the witness into
|
|
75
|
+
* `environmentWitness`. Filled with this call's reading when it takes one.
|
|
76
|
+
*/
|
|
77
|
+
environmentWitnesses?: Map<string, PluginBuildEnvironmentWitness.Record>;
|
|
78
|
+
|
|
69
79
|
/**
|
|
70
80
|
* Receives selected native toolchain dependencies from the environment reader
|
|
71
81
|
* for later comparison under its metadata-distinguishability premise; it is
|
|
@@ -77,6 +87,14 @@ export function computeCacheKey(inputs: {
|
|
|
77
87
|
filesystem?: Partial<SourceBuildFilesystemOperations>;
|
|
78
88
|
goBinary?: string;
|
|
79
89
|
goModReader?: SourcePluginWorkspace.GoModReader;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Record store that proves unchanged sources, overlays, contributors, GOROOT
|
|
93
|
+
* and executables from metadata in a new process (#1722). Without it every
|
|
94
|
+
* process reads their bytes.
|
|
95
|
+
*/
|
|
96
|
+
identities?: PluginContentIdentities.Store;
|
|
97
|
+
|
|
80
98
|
overlayDirs?: readonly string[];
|
|
81
99
|
|
|
82
100
|
/**
|
|
@@ -128,61 +146,96 @@ export function computeCacheKey(inputs: {
|
|
|
128
146
|
// binaries produced by the former absolute-ancestry layout out of this
|
|
129
147
|
// layout's cache admissions, including callers with no SDK overlays.
|
|
130
148
|
hash.update(JSON.stringify(["external-source-layout", 1]));
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
hash.update(data);
|
|
138
|
-
environment.update(data);
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
goBinary,
|
|
142
|
-
inputs.dir,
|
|
143
|
-
env,
|
|
144
|
-
filesystem,
|
|
145
|
-
inputs.environmentWitness,
|
|
146
|
-
);
|
|
147
|
-
inputs.environmentDigests?.set(
|
|
148
|
-
path.resolve(inputs.dir),
|
|
149
|
-
environment.digest("hex"),
|
|
149
|
+
const overlays = [...(inputs.overlayDirs ?? [])].sort();
|
|
150
|
+
// Hash contributors in sorted-by-name order so two consumers with the
|
|
151
|
+
// same logical set produce the same key regardless of declaration order
|
|
152
|
+
// in the host's plugin descriptor.
|
|
153
|
+
const sortedContributors = [...(inputs.contributors ?? [])].sort((a, b) =>
|
|
154
|
+
a.name === b.name ? 0 : a.name < b.name ? -1 : 1,
|
|
150
155
|
);
|
|
151
|
-
|
|
156
|
+
// A store whose root lies inside a directory this key hashes would write
|
|
157
|
+
// records into that source, so such a store proves nothing here. The build
|
|
158
|
+
// rejects that cache placement (`SourcePluginAdmission`) once every source
|
|
159
|
+
// is known; replacement targets narrow the store below, before their own
|
|
160
|
+
// digests, since only Go's reading of the manifest names them.
|
|
161
|
+
let identities = PluginContentIdentities.outside(inputs.identities, [
|
|
162
|
+
inputs.dir,
|
|
163
|
+
...overlays,
|
|
164
|
+
...sortedContributors.map((contributor) => contributor.source),
|
|
165
|
+
]);
|
|
166
|
+
// The key frames the environment digest pluginBuildEnvironment reports for
|
|
167
|
+
// this directory. WARNING (#1721): package selection reads that environment
|
|
168
|
+
// first in the same load; reading it again here doubled `go env` and
|
|
169
|
+
// `go version` per load. A reading taken through another byte adapter is
|
|
170
|
+
// not this one's, so only the default adapter shares it.
|
|
171
|
+
const environmentDirectory = path.resolve(inputs.dir);
|
|
172
|
+
const sharedDigest =
|
|
173
|
+
inputs.filesystem === undefined
|
|
174
|
+
? inputs.environmentDigests?.get(environmentDirectory)
|
|
175
|
+
: undefined;
|
|
176
|
+
const sharedWitness =
|
|
177
|
+
sharedDigest === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: inputs.environmentWitnesses?.get(environmentDirectory);
|
|
180
|
+
let environment: string;
|
|
181
|
+
if (sharedDigest !== undefined && sharedWitness !== undefined) {
|
|
182
|
+
// The build compares this witness after it ran, exactly as it would its
|
|
183
|
+
// own reading's, so a toolchain change since the reading still refuses.
|
|
184
|
+
for (const [file, state] of sharedWitness)
|
|
185
|
+
if (!inputs.environmentWitness?.has(file))
|
|
186
|
+
inputs.environmentWitness?.set(file, state);
|
|
187
|
+
environment = sharedDigest;
|
|
188
|
+
} else {
|
|
189
|
+
const witness =
|
|
190
|
+
inputs.environmentWitness ??
|
|
191
|
+
(inputs.environmentWitnesses === undefined ? undefined : new Map());
|
|
192
|
+
const reading = crypto.createHash("sha256");
|
|
193
|
+
hashPluginBuildEnvironment(
|
|
194
|
+
reading,
|
|
195
|
+
goBinary,
|
|
196
|
+
inputs.dir,
|
|
197
|
+
env,
|
|
198
|
+
filesystem,
|
|
199
|
+
witness,
|
|
200
|
+
identities,
|
|
201
|
+
);
|
|
202
|
+
environment = reading.digest("hex");
|
|
203
|
+
inputs.environmentDigests?.set(environmentDirectory, environment);
|
|
204
|
+
if (inputs.filesystem === undefined && witness !== undefined)
|
|
205
|
+
inputs.environmentWitnesses?.set(environmentDirectory, new Map(witness));
|
|
206
|
+
}
|
|
207
|
+
hash.update(JSON.stringify(["environment", environment]));
|
|
208
|
+
const directory = (label: string, root: string): void =>
|
|
209
|
+
hashSourceDirectory(hash, label, root, inputs.sourceDigests, identities);
|
|
210
|
+
directory("plugin", inputs.dir);
|
|
152
211
|
// Local replacement targets outside the module supply separately keyed
|
|
153
212
|
// sources. buildSourcePlugin snapshots and proves their copies before
|
|
154
|
-
// redirecting the Go replacement directives to the scratch tree.
|
|
155
|
-
|
|
213
|
+
// redirecting the Go replacement directives to the scratch tree. Go reads the
|
|
214
|
+
// manifest after the plugin's digest, so an edit made meanwhile lands between
|
|
215
|
+
// that digest and the build's copy, which `requireKeyedSource` refuses.
|
|
216
|
+
const replacements = pluginModuleReplaceDirectories(
|
|
156
217
|
inputs.dir,
|
|
157
218
|
env,
|
|
158
219
|
goBinary,
|
|
159
220
|
inputs.goModReader,
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
221
|
+
);
|
|
222
|
+
identities = PluginContentIdentities.outside(
|
|
223
|
+
identities,
|
|
224
|
+
replacements.map((replacement) => replacement.directory),
|
|
225
|
+
);
|
|
226
|
+
for (const replacement of replacements) {
|
|
227
|
+
directory(
|
|
163
228
|
`replace:${replacement.modulePath}${
|
|
164
229
|
replacement.version === undefined ? "" : `@${replacement.version}`
|
|
165
230
|
}`,
|
|
166
231
|
replacement.directory,
|
|
167
|
-
inputs.sourceDigests,
|
|
168
232
|
);
|
|
169
233
|
}
|
|
170
|
-
for (const [index, dir] of
|
|
171
|
-
|
|
234
|
+
for (const [index, dir] of overlays.entries()) {
|
|
235
|
+
directory(`overlay:${index}`, dir);
|
|
172
236
|
}
|
|
173
|
-
// Hash contributors in sorted-by-name order so two consumers with the
|
|
174
|
-
// same logical set produce the same key regardless of declaration order
|
|
175
|
-
// in the host's plugin descriptor.
|
|
176
|
-
const sortedContributors = [...(inputs.contributors ?? [])].sort((a, b) =>
|
|
177
|
-
a.name === b.name ? 0 : a.name < b.name ? -1 : 1,
|
|
178
|
-
);
|
|
179
237
|
for (const contributor of sortedContributors) {
|
|
180
|
-
|
|
181
|
-
hash,
|
|
182
|
-
`contributor:${contributor.name}`,
|
|
183
|
-
contributor.source,
|
|
184
|
-
inputs.sourceDigests,
|
|
185
|
-
);
|
|
238
|
+
directory(`contributor:${contributor.name}`, contributor.source);
|
|
186
239
|
}
|
|
187
240
|
return hash.digest("hex").slice(0, 32);
|
|
188
241
|
}
|
|
@@ -197,11 +250,14 @@ function hashSourceDirectory(
|
|
|
197
250
|
label: string,
|
|
198
251
|
root: string,
|
|
199
252
|
digests: Map<string, string> | undefined,
|
|
253
|
+
identities: PluginContentIdentities.Store | undefined,
|
|
200
254
|
): void {
|
|
201
255
|
const directory = path.resolve(root);
|
|
202
256
|
let digest = digests?.get(directory);
|
|
203
257
|
if (digest === undefined) {
|
|
204
|
-
|
|
258
|
+
// WARNING (#1722): the load-scoped map above ends with the process. Only the
|
|
259
|
+
// record store spares a new process from reading every source byte again.
|
|
260
|
+
digest = PluginContentIdentities.sourceDirectory(identities, directory);
|
|
205
261
|
digests?.set(directory, digest);
|
|
206
262
|
}
|
|
207
263
|
hash.update(JSON.stringify(["dir", label, digest]));
|