@openclaw/signal 2026.11.3 → 2026.11.5-beta.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/dist/.setup/{accounts-B0Pba1-q.mjs → accounts-enJKL3ci.mjs} +4 -8
- package/dist/.setup/api-CrasQEKI.mjs +14 -0
- package/dist/.setup/{api-CbN3mye4.mjs → api-DljZsfUn.mjs} +9 -11
- package/dist/.setup/{approval-auth-DhUKkSkA.mjs → approval-auth-B_5Ytx8K.mjs} +7 -11
- package/dist/.setup/{approval-handler.runtime-CsEcBZoc.mjs → approval-handler.runtime-0RbVmTxx.mjs} +7 -8
- package/dist/.setup/{approval-reactions-JsqCEOHK.mjs → approval-reactions-CvqVSSx8.mjs} +16 -24
- package/dist/.setup/{channel-CM-rLLPN.mjs → channel-ByJj8fSh.mjs} +417 -372
- package/dist/.setup/{client-Duwqbgqi.mjs → client-CesmEjRc.mjs} +170 -186
- package/dist/.setup/{config-schema-DvxZ18SD.mjs → config-schema-BCmW0BLB.mjs} +2 -5
- package/dist/.setup/{doctor-contract-api-BIdVW0PF.mjs → doctor-contract-api-CcLwIhsD.mjs} +88 -95
- package/dist/.setup/{identity-B3JyPqGO.mjs → identity-vKtR3DrM.mjs} +39 -2
- package/dist/.setup/{install-signal-cli-hYVLYGXe.mjs → install-signal-cli-DvLDZfpP.mjs} +95 -109
- package/dist/.setup/{transport-url-CAC0JcuI.mjs → legacy-transport-D_cZC3bS.mjs} +18 -1
- package/dist/.setup/{monitor-C6u1JV2C.mjs → monitor-Cf5sihwl.mjs} +166 -239
- package/dist/.setup/{probe-D_93QCe0.mjs → probe-Caq5Y4kA.mjs} +3 -3
- package/dist/.setup/{send-CM7-016e.mjs → send-BJuOWLky.mjs} +34 -66
- package/dist/.setup/{setup-surface-DvKLwYrl.mjs → setup-surface-DQ1yFZmY.mjs} +3 -3
- package/dist/.setup/{transport-detection-CmlgOybe.mjs → transport-detection-DvR6X_UQ.mjs} +3 -3
- package/dist/.setup/{transport-policy-B6C_LogW.mjs → transport-policy-zIR3lRUj.mjs} +1 -1
- package/dist/.setup/transport-probes.runtime-CoH6UFtc.mjs +1 -0
- package/dist/.source-module-urls/b02c526df8b90083105b63e5b51d5c8bbfe02676df72f6be8dd8f825817b32b9.json +1 -0
- package/dist/api.js +9 -11
- package/dist/channel-config-api.js +1 -2
- package/dist/channel-plugin-api.js +1 -2
- package/dist/contract-api.js +3 -4
- package/dist/doctor-contract-api.js +1 -2
- package/dist/runtime-api.js +1 -2
- package/dist/setup-entry.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +94 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/file-open-error.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-open-error.js +14 -0
- package/node_modules/@openclaw/fs-safe/dist/file-sync.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/file-sync.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +14 -3
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +9 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +40 -32
- package/node_modules/@openclaw/fs-safe/dist/output.js +6 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +25 -2
- package/node_modules/@openclaw/fs-safe/dist/publication-handle.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/publication-handle.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +19 -9
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +23 -24
- package/node_modules/@openclaw/fs-safe/dist/publish-rename-fallback.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-rename-fallback.js +102 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +19 -9
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +27 -13
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +18 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +68 -21
- package/node_modules/@openclaw/fs-safe/dist/watch-io-error.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io-error.js +27 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io-worker.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io.d.ts +47 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io.js +236 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-probe.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-probe.js +135 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +86 -24
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +78 -16
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +23 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/watch.js +60 -13
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +5 -2
- package/node_modules/@openclaw/fs-safe/docs/compiler-qualification.md +392 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +25 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +10 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +9 -3
- package/node_modules/@openclaw/fs-safe/docs/errors.md +13 -0
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -4
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +6 -4
- package/node_modules/@openclaw/fs-safe/docs/output.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/path.md +5 -2
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +15 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +9 -0
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +62 -5
- package/node_modules/@openclaw/fs-safe/docs/writing.md +6 -1
- package/node_modules/@openclaw/fs-safe/package.json +18 -16
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/node_modules/zod/package.json +1 -1
- package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/zod/v4/core/versions.js +1 -1
- package/openclaw.plugin.json +0 -30
- package/package.json +6 -8
- package/dist/.setup/api-CJBtD3lT.mjs +0 -16
- package/dist/.setup/client-adapter-Chnbch_z.mjs +0 -30
- package/dist/.setup/legacy-transport-QIpaMNST.mjs +0 -18
- package/dist/.setup/normalize-D7kSomXt.mjs +0 -39
- package/dist/.setup/reaction-runtime-api-mdO4k0yv.mjs +0 -65
- package/dist/.setup/reaction-targets-BgPLDHBf.mjs +0 -72
- package/dist/.setup/rpc-context-BAXcX7dI.mjs +0 -13
- package/dist/.setup/transport-probes.runtime-D-xIMRcn.mjs +0 -2
- package/dist/reaction-runtime-api.js +0 -2
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -22
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -530
- package/node_modules/ws/lib/receiver.js +0 -743
- package/node_modules/ws/lib/sender.js +0 -607
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -562
- package/node_modules/ws/lib/websocket.js +0 -1421
- package/node_modules/ws/package.json +0 -74
- package/node_modules/ws/wrapper.mjs +0 -21
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
2
|
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
3
|
-
import { assertRootIdentityCurrent } from "./
|
|
3
|
+
import { createWatchIo, watchRootCurrent as assertRootIdentityCurrent, watchYield } from "./watch-io.js";
|
|
4
4
|
import { rootHandleContext } from "./root-handle-context.js";
|
|
5
5
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
6
6
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
7
|
+
import { probeWatchSnapshot, verifyWatchReuse } from "./watch-probe.js";
|
|
7
8
|
import { mergeWatchRescan, watchRescanScopes } from "./watch-rescan.js";
|
|
8
9
|
import { admittedNativeChanges, changedEntries, excludedWatchPath, guardedHintChanges, scopedChanges } from "./watch-hints.js";
|
|
9
10
|
import path from "node:path";
|
|
@@ -32,7 +33,7 @@ const retired = () => new DOMException("Watch generation retired", "AbortError")
|
|
|
32
33
|
const coalesceMs = 25;
|
|
33
34
|
/** Advisory observation only. Hints never grant filesystem authority. */
|
|
34
35
|
export function watch(root, input) {
|
|
35
|
-
const context = rootHandleContext(root);
|
|
36
|
+
const context = { ...rootHandleContext(root) };
|
|
36
37
|
const options = { ...input };
|
|
37
38
|
const persistent = options.persistent !== false;
|
|
38
39
|
if (!["auto", "events", "poll"].includes(options.mode))
|
|
@@ -54,6 +55,9 @@ export function watch(root, input) {
|
|
|
54
55
|
throw new RangeError("watch pollIntervalMs must be at least 20");
|
|
55
56
|
if (mode === "poll")
|
|
56
57
|
intervalMs = pollIntervalMs;
|
|
58
|
+
const fullReconcileIntervalMs = budget(options.fullReconcileIntervalMs, 300_000, "fullReconcileIntervalMs", 2_147_483_647);
|
|
59
|
+
if (fullReconcileIntervalMs < 20)
|
|
60
|
+
throw new RangeError("watch fullReconcileIntervalMs must be at least 20");
|
|
57
61
|
const maxDirectories = budget(options.maxDirectories, 4096, "maxDirectories");
|
|
58
62
|
const maxEntries = budget(options.maxEntries, 100_000, "maxEntries");
|
|
59
63
|
const maxPendingPaths = budget(options.maxPendingPaths, 256, "maxPendingPaths", 4096);
|
|
@@ -74,13 +78,17 @@ export function watch(root, input) {
|
|
|
74
78
|
let closing;
|
|
75
79
|
let active;
|
|
76
80
|
let backend;
|
|
81
|
+
let closeIo;
|
|
77
82
|
const registered = new Map();
|
|
78
83
|
let observedDirectories = 0;
|
|
79
84
|
let snapshot;
|
|
85
|
+
let hasBeenReady = false;
|
|
86
|
+
let lastFullReconcile = 0;
|
|
80
87
|
let resetRequested = false;
|
|
81
88
|
let refreshBackend = false;
|
|
82
89
|
let pending = false;
|
|
83
90
|
let fullRequested = false;
|
|
91
|
+
let periodicRequested = false;
|
|
84
92
|
let pendingUncertain = false;
|
|
85
93
|
let pendingWaiter;
|
|
86
94
|
let runningWaiter;
|
|
@@ -208,7 +216,7 @@ export function watch(root, input) {
|
|
|
208
216
|
const scheduleInterval = () => {
|
|
209
217
|
if (timer || fullRequested || terminal || failure !== undefined)
|
|
210
218
|
return;
|
|
211
|
-
timer = setTimeout(() => { timer = undefined; void request().catch(() => { }); }, intervalMs);
|
|
219
|
+
timer = setTimeout(() => { timer = undefined; periodicRequested = true; void request(false).catch(() => { }); }, Math.min(intervalMs, fullReconcileIntervalMs));
|
|
212
220
|
if (!persistent)
|
|
213
221
|
timer.unref();
|
|
214
222
|
};
|
|
@@ -271,7 +279,7 @@ export function watch(root, input) {
|
|
|
271
279
|
hintTimer.unref();
|
|
272
280
|
};
|
|
273
281
|
const fallBack = (error) => {
|
|
274
|
-
if (options.mode !== "auto" || getFsSafeNativeConfig().mode === "require" ||
|
|
282
|
+
if (hasBeenReady || options.mode !== "auto" || getFsSafeNativeConfig().mode === "require" ||
|
|
275
283
|
!(error instanceof FsSafeError) || error.code !== "helper-unavailable")
|
|
276
284
|
return false;
|
|
277
285
|
mode = "poll";
|
|
@@ -281,6 +289,7 @@ export function watch(root, input) {
|
|
|
281
289
|
};
|
|
282
290
|
const observe = async (g) => {
|
|
283
291
|
check(g);
|
|
292
|
+
closeIo ??= createWatchIo(context, persistent, retainRetirement);
|
|
284
293
|
if (selectionFailure !== undefined)
|
|
285
294
|
throw new FsSafeError("helper-unavailable", "native watch events are unavailable", { cause: selectionFailure, details: { operation: "watch" } });
|
|
286
295
|
if (refreshBackend) {
|
|
@@ -298,7 +307,10 @@ export function watch(root, input) {
|
|
|
298
307
|
const hadHints = pendingHint;
|
|
299
308
|
const hadBackendOverflow = pendingBackendOverflow;
|
|
300
309
|
const hints = pendingChanges;
|
|
301
|
-
const
|
|
310
|
+
const age = performance.now() - lastFullReconcile;
|
|
311
|
+
const full = fullRequested || pendingUncertain || age < 0 || age >= fullReconcileIntervalMs;
|
|
312
|
+
const periodic = periodicRequested;
|
|
313
|
+
periodicRequested = false;
|
|
302
314
|
fullRequested = false;
|
|
303
315
|
pendingUncertain = false;
|
|
304
316
|
pendingHint = false;
|
|
@@ -307,7 +319,7 @@ export function watch(root, input) {
|
|
|
307
319
|
clearTimeout(hintTimer);
|
|
308
320
|
hintTimer = undefined;
|
|
309
321
|
let scanScopes = g.scopes;
|
|
310
|
-
if (snapshot && hadHints && hints && !full) {
|
|
322
|
+
if (snapshot && hadHints && hints && !full && !periodic) {
|
|
311
323
|
try {
|
|
312
324
|
const admitted = await admittedNativeChanges(context, g.scopes, snapshot, snapshot, {
|
|
313
325
|
hints: [...hints.values()], overflow: false,
|
|
@@ -329,6 +341,19 @@ export function watch(root, input) {
|
|
|
329
341
|
state = snapshot ? "reconciling" : "starting";
|
|
330
342
|
notifyHealth();
|
|
331
343
|
check(g);
|
|
344
|
+
const probe = snapshot && !full && (!hadHints || periodic)
|
|
345
|
+
? await probeWatchSnapshot(context, snapshot, g.abort.signal, options.exclude) : undefined;
|
|
346
|
+
check(g);
|
|
347
|
+
let reuse = probe?.reuse;
|
|
348
|
+
if (snapshot && probe?.stable && !hadHints && (mode === "poll" || backend) &&
|
|
349
|
+
await verifyWatchReuse(context, snapshot, probe.reuse, g.abort.signal)) {
|
|
350
|
+
check(g);
|
|
351
|
+
state = "ready";
|
|
352
|
+
notifyHealth();
|
|
353
|
+
check(g);
|
|
354
|
+
scheduleInterval();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
332
357
|
if (mode === "events" && !backend && g.scopes.length) {
|
|
333
358
|
try {
|
|
334
359
|
const candidate = new NativeWatchBackend(binding, context, batch => {
|
|
@@ -337,7 +362,7 @@ export function watch(root, input) {
|
|
|
337
362
|
}, maxPendingPaths, persistent);
|
|
338
363
|
backend = candidate;
|
|
339
364
|
if (snapshot)
|
|
340
|
-
candidate.configure(snapshot, g.scopes);
|
|
365
|
+
await candidate.configure(snapshot, g.scopes);
|
|
341
366
|
const hookResult = getFsSafeTestHooks()?.afterWatchBackendCreated?.(context.rootReal, batch => {
|
|
342
367
|
if (backend === candidate)
|
|
343
368
|
onHint(g, batch);
|
|
@@ -357,7 +382,7 @@ export function watch(root, input) {
|
|
|
357
382
|
timer = undefined;
|
|
358
383
|
}
|
|
359
384
|
try {
|
|
360
|
-
next = await scanWatch(context, scanScopes, { exclude: options.exclude, maxDirectories, maxEntries, maxPendingPaths, admitting: !snapshot, previous: snapshot }, g.abort.signal, async (name, identity, guard) => {
|
|
385
|
+
next = await scanWatch(context, scanScopes, { exclude: options.exclude, maxDirectories, maxEntries, maxPendingPaths, admitting: !snapshot, previous: snapshot, reuse }, g.abort.signal, async (name, identity, guard) => {
|
|
361
386
|
check(g);
|
|
362
387
|
const existing = registered.get(name);
|
|
363
388
|
const acquire = !existing || existing.dev !== identity.dev || existing.ino !== identity.ino;
|
|
@@ -390,8 +415,13 @@ export function watch(root, input) {
|
|
|
390
415
|
throw error;
|
|
391
416
|
}
|
|
392
417
|
check(g);
|
|
418
|
+
if (reuse && !await verifyWatchReuse(context, snapshot, reuse, g.abort.signal)) {
|
|
419
|
+
reuse = undefined;
|
|
420
|
+
scanScopes = g.scopes;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
393
423
|
if (scanScopes !== g.scopes) {
|
|
394
|
-
const merged = mergeWatchRescan(g.scopes, snapshot, next, scanScopes);
|
|
424
|
+
const merged = await mergeWatchRescan(g.scopes, snapshot, next, scanScopes);
|
|
395
425
|
if (!merged || merged.scanned > maxEntries || merged.directories.size > maxDirectories || (merged.excluded?.size ?? 0) > maxEntries) {
|
|
396
426
|
scanScopes = g.scopes;
|
|
397
427
|
continue;
|
|
@@ -400,7 +430,7 @@ export function watch(root, input) {
|
|
|
400
430
|
}
|
|
401
431
|
try {
|
|
402
432
|
const entriesChanged = backend?.entries(next);
|
|
403
|
-
const streamsChanged = backend?.configure(next, g.scopes);
|
|
433
|
+
const streamsChanged = await backend?.configure(next, g.scopes);
|
|
404
434
|
if (entriesChanged || streamsChanged) {
|
|
405
435
|
// Complete a guarded pass with the new transport before publishing readiness.
|
|
406
436
|
// Bound handovers under churn; later passes still reconcile ongoing changes.
|
|
@@ -415,6 +445,11 @@ export function watch(root, input) {
|
|
|
415
445
|
catch (error) {
|
|
416
446
|
await assertRootIdentityCurrent(context);
|
|
417
447
|
check(g);
|
|
448
|
+
if (!snapshot && fallBack(error)) {
|
|
449
|
+
await retireBackend();
|
|
450
|
+
check(g);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
418
453
|
if (attempt >= 2 || !isWatchPathError(error))
|
|
419
454
|
throw error;
|
|
420
455
|
scanScopes = g.scopes;
|
|
@@ -422,13 +457,18 @@ export function watch(root, input) {
|
|
|
422
457
|
}
|
|
423
458
|
}
|
|
424
459
|
// Retire stale inventory before the next pass; that crawl installs fresh anchors first.
|
|
425
|
-
|
|
426
|
-
|
|
460
|
+
const checkpoint = watchYield();
|
|
461
|
+
for (const name of registered.keys()) {
|
|
462
|
+
await checkpoint();
|
|
463
|
+
if (!next.directories.has(name)) {
|
|
464
|
+
refreshBackend = true;
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
427
467
|
}
|
|
428
468
|
observedDirectories = backend?.directories === undefined ? next.directories.size
|
|
429
469
|
: backend.directories + (g.scopes.some(scope => scope.kind === "tree") ? next.directories.size : 0);
|
|
430
470
|
const initial = !snapshot;
|
|
431
|
-
let observed = next.overflow ? undefined : changedEntries(snapshot, next, maxPendingPaths);
|
|
471
|
+
let observed = next.overflow ? undefined : await changedEntries(snapshot, next, maxPendingPaths);
|
|
432
472
|
if (observed) {
|
|
433
473
|
const changes = new Map(observed.map(change => [change.path, change]));
|
|
434
474
|
for (const name of next.structural ?? [])
|
|
@@ -457,6 +497,8 @@ export function watch(root, input) {
|
|
|
457
497
|
? guardedHintChanges(g.scopes, snapshot, next, admittedHints, observed, maxPendingPaths)
|
|
458
498
|
: observed;
|
|
459
499
|
snapshot = next;
|
|
500
|
+
if (!reuse && scanScopes === g.scopes)
|
|
501
|
+
lastFullReconcile = performance.now();
|
|
460
502
|
if (!initial && !details && hadBackendOverflow) {
|
|
461
503
|
assertSynchronousCallbackResult(getFsSafeTestHooks()?.afterWatchBackendOverflow?.(context.rootReal, "reconciled"), "afterWatchBackendOverflow");
|
|
462
504
|
}
|
|
@@ -464,6 +506,7 @@ export function watch(root, input) {
|
|
|
464
506
|
if (initial || !details || details.length)
|
|
465
507
|
dirty(g, initial ? "reconcile" : !details ? "overflow" : hadHints ? "event" : "reconcile", initial ? undefined : details);
|
|
466
508
|
check(g);
|
|
509
|
+
hasBeenReady = true;
|
|
467
510
|
state = "ready";
|
|
468
511
|
notifyHealth();
|
|
469
512
|
check(g);
|
|
@@ -503,6 +546,8 @@ export function watch(root, input) {
|
|
|
503
546
|
catch (closeError) {
|
|
504
547
|
retainRetirement(closeError);
|
|
505
548
|
}
|
|
549
|
+
await closeIo?.();
|
|
550
|
+
closeIo = undefined;
|
|
506
551
|
}
|
|
507
552
|
if (current !== g) {
|
|
508
553
|
await retireBackend();
|
|
@@ -564,6 +609,8 @@ export function watch(root, input) {
|
|
|
564
609
|
catch (error) {
|
|
565
610
|
retainRetirement(error);
|
|
566
611
|
}
|
|
612
|
+
await closeIo?.();
|
|
613
|
+
closeIo = undefined;
|
|
567
614
|
state = "closed";
|
|
568
615
|
if (retirementFailure)
|
|
569
616
|
throw combinedFailure();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NativeWindowsSecurityFacts } from "./native-binding.js";
|
|
2
2
|
export declare const APP_CONTAINER_SID_RE: RegExp;
|
|
3
|
-
export type DescriptorFacts = Pick<NativeWindowsSecurityFacts, "ownerSid" | "currentUserSid" | "currentAppContainerSid" | "daclPresent" | "isLocal" | "aceListComplete" | "unsupportedAceTypes" | "aces">;
|
|
3
|
+
export type DescriptorFacts = Pick<NativeWindowsSecurityFacts, "ownerSid" | "currentUserSid" | "currentAppContainerSid" | "daclPresent" | "isLocal" | "isVolumeRoot" | "aceListComplete" | "unsupportedAceTypes" | "aces">;
|
|
4
4
|
export declare function unverified(message: string, cause?: unknown): never;
|
|
5
5
|
/** Validate policy-free observations crossing the isolated batch transport. */
|
|
6
6
|
export declare function parseWindowsOwnerAndDaclFacts(value: unknown): DescriptorFacts;
|
|
@@ -37,6 +37,7 @@ function validateDescriptor(value, allowUnknownFlags) {
|
|
|
37
37
|
typeof value.currentUserSid !== "string" || !SID_RE.test(value.currentUserSid) ||
|
|
38
38
|
(value.currentAppContainerSid != null && (typeof value.currentAppContainerSid !== "string" || !APP_CONTAINER_SID_RE.test(value.currentAppContainerSid))) ||
|
|
39
39
|
typeof value.daclPresent !== "boolean" || typeof value.isLocal !== "boolean" ||
|
|
40
|
+
(value.isVolumeRoot !== undefined && typeof value.isVolumeRoot !== "boolean") ||
|
|
40
41
|
typeof value.aceListComplete !== "boolean" || !Array.isArray(value.unsupportedAceTypes) ||
|
|
41
42
|
!value.unsupportedAceTypes.every(uint32) || !Array.isArray(value.aces) ||
|
|
42
43
|
!value.aces.every(ace => validateAce(ace, allowUnknownFlags))) {
|
|
Binary file
|
|
@@ -117,8 +117,11 @@ is a validation failure and triggers one descriptor-close attempt. If cleanup al
|
|
|
117
117
|
fails, the selected admission failure result is preserved. Successful opens transfer
|
|
118
118
|
descriptor ownership to the caller. A custom `ioFs` supplies
|
|
119
119
|
these observations; the built-in adapter uses fs-safe's native realpath wrapper.
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
The built-in adapter binds native root spelling before traversal, including
|
|
121
|
+
supplied `rootRealPath`, and returns that spelling in its root receipt. If native
|
|
122
|
+
resolution changes the spelling (for example, case on macOS or short names on
|
|
123
|
+
Windows), the resolved directory must retain the captured identity. Differently
|
|
124
|
+
cased directories on case-sensitive filesystems remain distinct.
|
|
122
125
|
This is an operation-local detection fence, not atomic confinement against a peer
|
|
123
126
|
that can keep racing pathname bindings.
|
|
124
127
|
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compiler qualification investigation
|
|
3
|
+
description: "Hypotheses, experiments, and acceptance gates for tsc-ts and scriptc in fs-safe."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Compiler qualification investigation
|
|
7
|
+
|
|
8
|
+
Status: investigation plan, 2026-10-17. This document does not qualify a compiler, runtime, or package change.
|
|
9
|
+
|
|
10
|
+
Investigate two separate capabilities:
|
|
11
|
+
|
|
12
|
+
1. Use **tsc-ts** to check TypeScript and emit the existing Node.js package faster.
|
|
13
|
+
2. Use **scriptc** to compile fs-safe application code into native programs while preserving its safety contracts.
|
|
14
|
+
|
|
15
|
+
The second capability changes the runtime. A faster compiler does not establish faster filesystem operations.
|
|
16
|
+
Keep both investigations open until measurements and contract proof support a decision.
|
|
17
|
+
Prepare small draft PRs. Obtain explicit maintainer approval before merging, releasing, or deploying anything.
|
|
18
|
+
|
|
19
|
+
## Frozen baseline
|
|
20
|
+
|
|
21
|
+
| Input | Investigation identity |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| fs-safe | `0f396cd9cbd05519a9600d95daadcf827bff6b76`, package `0.26.7` |
|
|
24
|
+
| Package manager | `pnpm@12.10.1` |
|
|
25
|
+
| Official compiler | Lockfile resolves `typescript@7.0.2` |
|
|
26
|
+
| tsc-ts source | `7e21c1012710cc438fd30da0f22f30e276d4222b` |
|
|
27
|
+
| scriptc source | `66125d783b0e03af6e28843229c10b6436333c08` |
|
|
28
|
+
| Initial candidate | Published `tsc-ts@0.0.2` + `@tsc-ts/linux-x64-gnu@0.0.2`; release tag target `b0dbfb0ea2240b59d51de5de1aae3a35a9f3dc10`, distinct from inspected main above. |
|
|
29
|
+
|
|
30
|
+
Do not equate a release binary with a later source revision that declares the same package version.
|
|
31
|
+
Freeze source, lockfile, compiler libraries, runner, flags, and binary identities for each comparison.
|
|
32
|
+
Changes to those inputs require new evidence for the affected experiment.
|
|
33
|
+
|
|
34
|
+
The frozen [tsconfig](https://github.com/openclaw/fs-safe/blob/0f396cd9cbd05519a9600d95daadcf827bff6b76/tsconfig.json) includes 317 source TypeScript files. It excludes the test tree and JavaScript tooling.
|
|
35
|
+
Its contract includes strict NodeNext, ES2022 output, declarations, `noUncheckedIndexedAccess`, and `verbatimModuleSyntax`.
|
|
36
|
+
|
|
37
|
+
The [build script](https://github.com/openclaw/fs-safe/blob/0f396cd9cbd05519a9600d95daadcf827bff6b76/scripts/prepack-build.mjs) invokes the official compiler through Node.
|
|
38
|
+
It then prepares the Windows helper and Rust/WASM archive artifact.
|
|
39
|
+
Measure the compiler separately from the complete build.
|
|
40
|
+
|
|
41
|
+
The [package API checker](https://github.com/openclaw/fs-safe/blob/0f396cd9cbd05519a9600d95daadcf827bff6b76/scripts/public-api-surface.mjs) imports `typescript/unstable/ast` and `typescript/unstable/sync`.
|
|
42
|
+
Keep that dependency when evaluating a different compiler CLI.
|
|
43
|
+
Its export-name and error-code checks do not prove complete declaration-signature parity.
|
|
44
|
+
|
|
45
|
+
## Hypotheses and experiments
|
|
46
|
+
|
|
47
|
+
| ID | Hypothesis | Experiment | Acceptance gate |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| H1 | tsc-ts preserves checking and emission. | Compare the official compiler and native tsc-ts on identical source, config, libraries, and consumer fixtures. | Equal exit status and diagnostics. Explain every emitted JS or declaration difference. |
|
|
50
|
+
| H2 | Compiler acceleration materially improves this package's workflow. | Measure compiler-only checking, emission, and complete builds separately. | Repeatable benefit on representative runners without hiding failed runs or memory regressions. |
|
|
51
|
+
| H3 | Candidate output preserves the published package contract. | Install each generated tarball and exercise all public subpaths, declarations, assets, and lifecycle behavior. | Consumer behavior and types remain compatible. No accidental fallback to official-compiler output. |
|
|
52
|
+
| H4 | scriptc supports the host mechanisms fs-safe requires. | Probe real native loading, exact identities, flags, disposal, Workers, and WASM before a whole-library build. | Preserve every required mechanism or identify a reviewed architecture change. Unsupported remains unsupported. |
|
|
53
|
+
| H5 | Native compilation helps actual filesystem workloads. | Measure lexical work, guarded I/O, archives, and watch workloads after semantic qualification. | Representative end-to-end gains with unchanged safety, cancellation, and resource ownership. |
|
|
54
|
+
| H6 | Differential findings reveal ordinary fs-safe or consumer bugs. | Reproduce each discrepancy with the official compiler and normal Node/Bun. | Establish the violated contract and repair its owner independently of compiler adoption. |
|
|
55
|
+
| H7 | A fork is necessary and maintainable. | Minimize an upstream blocker, identify its owner, and test the smallest patch. | Accepted requirement, reproducible defect, bounded maintenance cost, and upstream synchronization plan. |
|
|
56
|
+
|
|
57
|
+
### Compiler parity protocol
|
|
58
|
+
|
|
59
|
+
1. Record source and lockfile hashes, effective config, resolved input files, compiler versions, libraries, and binary hashes.
|
|
60
|
+
2. Compare the locked official compiler with the candidate native executable.
|
|
61
|
+
3. Use the matching TypeScript-Go oracle and Node-hosted tsc-ts to localize disagreements when needed.
|
|
62
|
+
4. Compare diagnostics by category, code, relative file, span, and message.
|
|
63
|
+
5. Include valid consumers and intentional type errors. A candidate must not silently omit invalid inputs.
|
|
64
|
+
6. Compare emitted file inventories and bytes, including every `.js` and `.d.ts` file.
|
|
65
|
+
7. Investigate differences before introducing normalization. Preserve error locations and meaningful output differences.
|
|
66
|
+
8. Compile consumer fixtures against both declaration sets. Cover overloads, generics, unions, disposal, defaults, and public subpaths.
|
|
67
|
+
|
|
68
|
+
Reuse upstream corpus and package tests where they answer the same question.
|
|
69
|
+
The inspected [upstream benchmark](https://github.com/vercel-labs/tsc-ts/blob/7e21c1012710cc438fd30da0f22f30e276d4222b/packages/tsc-ts/scripts/bench.mjs#L77-L106) needs care.
|
|
70
|
+
Its whole-project timer ignores child status, and that comparison omits native tsc-ts.
|
|
71
|
+
Record this as an upstream follow-up. Do not use that timing table as fs-safe qualification.
|
|
72
|
+
|
|
73
|
+
### First harness
|
|
74
|
+
|
|
75
|
+
Run `scripts/compiler-qualification.mjs` on an isolated validation host after
|
|
76
|
+
`pnpm install --frozen-lockfile`. Supply the candidate's native executable,
|
|
77
|
+
not its npm JavaScript launcher. Keep its installer provenance separately:
|
|
78
|
+
release, source commit, npm integrity, platform package, and download origin.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
node scripts/compiler-qualification.mjs \
|
|
82
|
+
--candidate /path/to/native/tsc-ts \
|
|
83
|
+
--output /path/to/new-evidence-directory
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The output directory must be new and its parent must exist. The harness resolves
|
|
87
|
+
the locked official `typescript@7.0.2` and its matching platform package, then
|
|
88
|
+
uses both native executables directly. It sets `TSC_TS_LIB_DIR` to the official
|
|
89
|
+
platform package's libraries and verifies the actual resolved input bytes.
|
|
90
|
+
The official package remains installed for the existing API checker.
|
|
91
|
+
The harness clears inherited `TSC_TS_*`, `GOGC`, `GOMEMLIMIT`, `GOMAXPROCS`, and
|
|
92
|
+
`GODEBUG` tuning before setting the library directory. It records cleared names.
|
|
93
|
+
|
|
94
|
+
The harness requires identical effective configs, source checks, one valid and
|
|
95
|
+
one deliberately invalid language fixture, and emitted JS/declaration inventories
|
|
96
|
+
and bytes before timing. It records CLI diagnostic **start-location parity**;
|
|
97
|
+
CLI text does not expose diagnostic end positions or lengths. Structured-span
|
|
98
|
+
and public declaration-consumer proof remain separate experiments.
|
|
99
|
+
|
|
100
|
+
`report.json` records hashes, versions, effective config, input/output inventories,
|
|
101
|
+
process status and streams, and every successful timing sample. A failed process
|
|
102
|
+
remains in the process log and makes the entire campaign fail. No failed campaign
|
|
103
|
+
qualifies performance. Initial and final hashes reject changed source, inputs,
|
|
104
|
+
compiler executables, libraries, manifests, and retained output.
|
|
105
|
+
|
|
106
|
+
Default timing is two warmups and ten measured runs per compiler for each of
|
|
107
|
+
checking and emission, interleaved in ABBA/BAAB blocks. Counts must be positive
|
|
108
|
+
even integers. `--single-threaded` passes the same flag to both compilers;
|
|
109
|
+
unsupported flags fail the campaign. `--timeout-ms` defaults to 120000 per process.
|
|
110
|
+
This first harness measures **wall time only**. CPU time and peak RSS remain
|
|
111
|
+
unmeasured. Filesystem cache state is uncontrolled. It does not qualify the
|
|
112
|
+
complete build, installed package, target runtime, or a compiler replacement.
|
|
113
|
+
|
|
114
|
+
Use `official/` and `candidate/` only as retained compiler-output artifacts.
|
|
115
|
+
Successful timing samples discard their duplicate emit directories after hashing.
|
|
116
|
+
The harness never writes `dist` or invokes a packaging command that rebuilds it.
|
|
117
|
+
Review the report for private diagnostics before publishing it.
|
|
118
|
+
|
|
119
|
+
### Performance protocol
|
|
120
|
+
|
|
121
|
+
Use one isolated runner without competing builds. Record CPU allocation, operating system, filesystem, runtime, and resource limits.
|
|
122
|
+
Compare default concurrency and equal checker counts. Report single-threaded results separately.
|
|
123
|
+
|
|
124
|
+
Start with two warmups and ten measured runs per configuration, with interleaved ABBA/BAAB order.
|
|
125
|
+
Report median, spread, wall time, CPU time, and peak RSS. Exclude warmups from reported samples.
|
|
126
|
+
Classify nonzero exits, signals, crashes, incomplete output, and timeouts as failures rather than fast samples.
|
|
127
|
+
Use a reviewed finite deadline for each experiment and report censored runs.
|
|
128
|
+
|
|
129
|
+
Distinguish fresh-process runs from filesystem-cache-cold runs. Do not claim cold caches without controlling them.
|
|
130
|
+
Measure incremental no-op and one-file edits only as optional scenarios because the current build does not enable incremental compilation.
|
|
131
|
+
Measure repeated API/watch operation separately if adoption later includes a persistent compiler process.
|
|
132
|
+
|
|
133
|
+
Select a material benefit threshold after measuring the baseline and before evaluating candidate timings.
|
|
134
|
+
The initial official-only pilot took about 0.37 seconds per check or emit on a
|
|
135
|
+
Linux x64 c7a.8xlarge runner. For this investigation, require at least 20% median
|
|
136
|
+
compiler-time savings and 10% whole-build savings before proposing optional
|
|
137
|
+
selection. Report absolute savings and complete-check impact as well; meeting
|
|
138
|
+
those thresholds does not replace compatibility or maintenance review.
|
|
139
|
+
Prefer whole-build savings and runner cost over a microbenchmark ratio.
|
|
140
|
+
Do not adopt a candidate that saves negligible time while adding substantial platform or maintenance cost.
|
|
141
|
+
|
|
142
|
+
### Package and runtime proof
|
|
143
|
+
|
|
144
|
+
Most runtime tests import source through Vitest. Those tests do not prove candidate-emitted JavaScript.
|
|
145
|
+
The experiment must preserve the identity of the generated package through every check.
|
|
146
|
+
|
|
147
|
+
Run the ordinary baseline through the existing repository commands:
|
|
148
|
+
Use a qualified validation host with frozen dependencies and the documented Rust/LLVM toolchain.
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
pnpm check
|
|
152
|
+
pnpm native:build
|
|
153
|
+
pnpm package:smoke
|
|
154
|
+
git diff --check
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For candidate output, compare compiler artifacts before packaging.
|
|
158
|
+
Prepare required native/WASM/helper artifacts through their existing owners.
|
|
159
|
+
Then run `node scripts/check-pack.mjs` against the candidate's `dist` in its isolated validation checkout.
|
|
160
|
+
This checker packs with `--ignore-scripts`, imports public subpaths, and runs installed-package smoke checks.
|
|
161
|
+
It also compiles installed consumers for root overloads, JSON stores, lock callbacks,
|
|
162
|
+
resource disposal, and discriminated results, with strict checking and exact optional properties.
|
|
163
|
+
To check those same consumers with the candidate compiler as well:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
node scripts/check-pack.mjs --candidate-compiler /path/to/native/tsc-ts
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Both compilers use the official standard library and the same installed tarball.
|
|
170
|
+
An unused `@ts-expect-error` control must fail before negative consumer cases are trusted.
|
|
171
|
+
The fixtures are compile-only; the existing package smoke tests own runtime behavior.
|
|
172
|
+
|
|
173
|
+
Do not run `pnpm pack:check` or `pnpm check` over candidate output and claim that output survived.
|
|
174
|
+
Those commands rebuild with the official compiler in the frozen baseline.
|
|
175
|
+
Do not update `test/public-api.json` merely to accept compiler differences.
|
|
176
|
+
|
|
177
|
+
Reuse the existing security cohort plus these representative lifecycle tests:
|
|
178
|
+
|
|
179
|
+
- `test/root-read-final-fence.test.ts`
|
|
180
|
+
- `test/atomic-restore-error-precedence.test.ts`
|
|
181
|
+
- `test/atomic-restoration-falsy-settlement.test.ts`
|
|
182
|
+
- `test/descriptor-close-ownership.test.ts`
|
|
183
|
+
- `test/root-create-native-integration.test.ts`
|
|
184
|
+
- `test/root-mkdir-post-create.test.ts`
|
|
185
|
+
- `test/root-write-lifetime.test.ts`
|
|
186
|
+
- `test/temp-workspace-cleanup-capability.test.ts`
|
|
187
|
+
- `test/native-worker-fd-ownership.test.ts`
|
|
188
|
+
- `test/native-init-context.test.ts`
|
|
189
|
+
- `test/value-adapter-contracts.test.ts`
|
|
190
|
+
- `test/public-api-surface.test.ts`
|
|
191
|
+
|
|
192
|
+
For runtime changes, preserve the documented [platform matrix](contributing.md) and [native capabilities](native.md).
|
|
193
|
+
Stage qualification from Linux to macOS and Windows, then the remaining supported native targets.
|
|
194
|
+
Cover Node 22/24/26, native `off`/`auto`/`require`, supported Bun lanes, glibc/musl, and FreeBSD's narrower native surface.
|
|
195
|
+
Use the existing Bun native proofs. The full Bun suite is diagnostic, not an assumed passing compatibility gate.
|
|
196
|
+
Keep cross-build success separate from execution on the target operating system.
|
|
197
|
+
|
|
198
|
+
Exercise concurrent replacement with an external process where runtime scheduling could bypass an in-process race fixture.
|
|
199
|
+
Observe rejection, file contents, identity, descriptor ownership, cleanup, error precedence, cancellation, and event-loop delay.
|
|
200
|
+
Successful returns alone do not prove safety or cleanup.
|
|
201
|
+
|
|
202
|
+
## scriptc feasibility gates
|
|
203
|
+
|
|
204
|
+
The frozen upstream explicitly excludes [N-API addons](https://github.com/vercel-labs/scriptc/blob/66125d783b0e03af6e28843229c10b6436333c08/docs/content/limitations/native-addons.mdx).
|
|
205
|
+
It also excludes [computed numeric flags](https://github.com/vercel-labs/scriptc/blob/66125d783b0e03af6e28843229c10b6436333c08/docs/content/limitations/builtin-apis/filesystem-flags.mdx)
|
|
206
|
+
and [BigInt filesystem stats](https://github.com/vercel-labs/scriptc/blob/66125d783b0e03af6e28843229c10b6436333c08/docs/content/limitations/builtin-apis/filehandle-and-descriptor-i-o.mdx).
|
|
207
|
+
Its filesystem operations complete synchronously before callbacks or promise continuations.
|
|
208
|
+
These documented limits are feasibility constraints, not newly demonstrated compiler defects.
|
|
209
|
+
|
|
210
|
+
| fs-safe owner | Required probe |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `src/native.ts`, `src/native-binding.ts` | Lazy addon loading, async-context isolation, per-operation capabilities, and exactly-once descriptor close. |
|
|
213
|
+
| `src/file-identity.ts`, `src/exact-identity.ts` | BigInt identity, safe-integer rejection, Windows unknown identity, getter evaluation, and cleanup authority. |
|
|
214
|
+
| `src/root-read-admission.ts`, `src/root-write-verification.ts` | Fresh boundary observations, hardlink rejection, retained-parent authority, and post-operation identity fences. |
|
|
215
|
+
| `src/byte-budget.ts`, `src/errors.ts`, `src/async-lock.ts`, `src/suppressed-error.ts` | Small pure-TS probes for numeric boundaries, error identity, FIFO release, and cleanup-error precedence. |
|
|
216
|
+
| `src/archive-tar-wasm.ts`, `src/archive-zip-loader.ts` | WASM memory growth, chunk limits, physical EOF, cancellation, disposal, optional imports, and object identity. |
|
|
217
|
+
| `src/watch-io.ts`, `src/directory-receipt.ts` | Worker lifetime, event ordering, and WeakMap-held capability identity. |
|
|
218
|
+
|
|
219
|
+
Start with minimal unchanged-code probes, then test the actual public package.
|
|
220
|
+
An import-only success or native-off run does not qualify the native-required path.
|
|
221
|
+
Never replace BigInt identity with Number, retained descriptors with pathnames, or independent observations with cached values to make compilation succeed.
|
|
222
|
+
|
|
223
|
+
If a C ABI adapter is necessary, review its design before implementation.
|
|
224
|
+
Prefer reusing the existing Rust mechanisms while keeping policy in TypeScript.
|
|
225
|
+
Specify handle ownership, errors, cancellation, platform behavior, and ABI compatibility first.
|
|
226
|
+
Do not create a second filesystem policy engine.
|
|
227
|
+
|
|
228
|
+
## Draft PR structure
|
|
229
|
+
|
|
230
|
+
Use dependency links only where one change actually requires another.
|
|
231
|
+
|
|
232
|
+
| PR | Scope | Prerequisite |
|
|
233
|
+
| --- | --- | --- |
|
|
234
|
+
| P1 | Compiler parity and measurement harness. Preserve official build defaults. | Frozen input and output identities, trustworthy failure accounting. |
|
|
235
|
+
| P2 | Candidate-package and declaration consumer proof missing from existing checks. | P1 artifact contract. |
|
|
236
|
+
| P3a, P3b, ... | One confirmed fs-safe or OpenClaw consumer defect per PR. | Minimal normal-runtime reproduction and owner agreement. |
|
|
237
|
+
| P4 | Optional compiler selection through the existing build owner. | H1-H3 pass and material measured benefit. Retain the official API dependency. |
|
|
238
|
+
| R1, R2, ... | Narrow runtime compatibility fixes or an accepted ABI design. | H4 findings, security-boundary review, and relevant platform proof. |
|
|
239
|
+
| U1, U2, ... | Reduced upstream regression tests and fixes. | Confirmed compiler/runtime owner and failing supported contract. |
|
|
240
|
+
|
|
241
|
+
P1 -> P2 -> P4 forms the compiler stack. Verified fixes join that dependency chain only when necessary.
|
|
242
|
+
Runtime work forms a separate reviewed stack. Do not couple ordinary Node fixes to compiler adoption.
|
|
243
|
+
Coordinate OpenClaw consumer changes with their existing owners and reuse shared benchmark evidence where inputs match.
|
|
244
|
+
|
|
245
|
+
## Fork decision
|
|
246
|
+
|
|
247
|
+
Choose the owning project: tsc-ts for checker/emitter defects, scriptc for lowering/runtime defects, fs-safe for its own invariant violations.
|
|
248
|
+
A documented unsupported feature is an architecture discussion before it is a fork justification.
|
|
249
|
+
|
|
250
|
+
Before creating a fork:
|
|
251
|
+
|
|
252
|
+
1. Preserve a minimal reproducer with expected and actual behavior.
|
|
253
|
+
2. Check the pinned version and current upstream for an existing fix.
|
|
254
|
+
3. Show that the blocker prevents an accepted fs-safe requirement.
|
|
255
|
+
4. Estimate the patch surface, platform tests, release ownership, and upstream synchronization cost.
|
|
256
|
+
5. Define an upstream contribution route and a fork retirement condition.
|
|
257
|
+
|
|
258
|
+
## Completion evidence
|
|
259
|
+
|
|
260
|
+
This plan is the first deliverable. It does not complete compiler qualification or runtime adoption.
|
|
261
|
+
Every experiment must report `pass`, `fail`, `unsupported`, or `not run`, with its exact inputs and evidence.
|
|
262
|
+
Keep the full objective open while required tests, verified fixes, stack preparation, or architecture decisions remain unresolved.
|
|
263
|
+
|
|
264
|
+
The final handoff must identify measured benefits, semantic differences, package/platform coverage, remaining blockers, and every draft PR dependency.
|
|
265
|
+
Keep source validation, CI status, merge approval, release publication, and deployed behavior separate.
|
|
266
|
+
No landing occurs without explicit maintainer approval.
|
|
267
|
+
|
|
268
|
+
## Initial Linux pilot
|
|
269
|
+
|
|
270
|
+
On Node 24.19.0 with pnpm 12.10.1, the published tsc-ts 0.0.2 Linux GNU binary
|
|
271
|
+
passed the first comparison: 508 identical resolved input hashes, 634 identical
|
|
272
|
+
emitted JavaScript/declaration files, and matching valid/invalid fixture results.
|
|
273
|
+
The official-versus-official control also passed. Each pilot used two warmups
|
|
274
|
+
and two measured samples per compiler per mode. These small pilots validate
|
|
275
|
+
harness execution; they do not establish a speedup or full package compatibility.
|
|
276
|
+
|
|
277
|
+
Native executable SHA-256 identities:
|
|
278
|
+
|
|
279
|
+
- Official TypeScript 7.0.2: `4f2de678286401759b3fb4475bafe35b8f32b4b3a07d92642bbf37eadc9b34a4`
|
|
280
|
+
- tsc-ts 0.0.2 Linux GNU: `fb24c9cc43533dc004b9dfc9547d7066cea348b807c3239f8e31c7ba062017b6`
|
|
281
|
+
|
|
282
|
+
The installed package's launcher selects `lib/tsc-ts` (`tsc-ts.exe` on Windows).
|
|
283
|
+
Record the npm package integrity from the experiment's installation lockfile;
|
|
284
|
+
do not infer binary provenance from its `Version 7.0.2` CLI output alone.
|
|
285
|
+
|
|
286
|
+
## Linux compiler and package results
|
|
287
|
+
|
|
288
|
+
The 2026-10-17 campaign used the same frozen source and executable identities
|
|
289
|
+
above, Linux x64 on an AWS c7a.8xlarge (32 logical CPUs), Node 24.19.0, and
|
|
290
|
+
pnpm 12.10.1. Each comparison used two warmups and ten measured samples per
|
|
291
|
+
compiler, in balanced ABBA/BAAB order. OS caches were uncontrolled.
|
|
292
|
+
|
|
293
|
+
| Invocation | Official median | tsc-ts median | Speedup |
|
|
294
|
+
| --- | --- | --- | --- |
|
|
295
|
+
| Default check | 387 ms | 114 ms | 3.40x |
|
|
296
|
+
| Default emit | 400 ms | 124 ms | 3.24x |
|
|
297
|
+
| Single-threaded check | 973 ms | 704 ms | 1.38x |
|
|
298
|
+
| Single-threaded emit | 1,041 ms | 863 ms | 1.21x |
|
|
299
|
+
| Complete warm build | 1,324 ms | 1,031 ms | 1.28x |
|
|
300
|
+
|
|
301
|
+
Both compiler configurations preserved all 508 resolved inputs and 634 emitted
|
|
302
|
+
JavaScript/declaration files. The complete build used the existing prepack
|
|
303
|
+
steps with only the compiler invocation substituted in a temporary runner.
|
|
304
|
+
Dependencies and Rust/.NET/WASM toolchains were warm; `dist` was recreated on
|
|
305
|
+
each invocation. All 636 complete-build artifacts matched across every run.
|
|
306
|
+
The median build saving was 22.1%, approximately 293 ms.
|
|
307
|
+
|
|
308
|
+
GNU time measured median build user CPU of 2.890/1.955 seconds, system CPU of
|
|
309
|
+
0.715/0.475 seconds, and maximum RSS of 310,372/261,192 KiB (official/candidate).
|
|
310
|
+
Maximum RSS is a process high-water mark, not summed concurrent memory.
|
|
311
|
+
Compiler-only CPU and RSS were not measured.
|
|
312
|
+
|
|
313
|
+
Candidate output also passed the installed-package checker without a rebuild:
|
|
314
|
+
public imports and API manifest, guest Python/rename protocol, directory receipt
|
|
315
|
+
runtime behavior, Root.walk declarations, and the three consumer fixtures under
|
|
316
|
+
both compilers. The complete `dist` inventory was unchanged after packing.
|
|
317
|
+
|
|
318
|
+
This clears the provisional Linux performance gate. It does not qualify cold
|
|
319
|
+
builds, macOS/Windows compiler binaries, other runtimes, or every native mode.
|
|
320
|
+
Identical emitted JavaScript provides no evidence of faster filesystem I/O.
|
|
321
|
+
Keep compiler adoption separate from these qualification tools.
|
|
322
|
+
|
|
323
|
+
## Platform and clean-output campaign
|
|
324
|
+
|
|
325
|
+
`compiler-qualification.yml` runs the published tsc-ts 0.0.2 native package on
|
|
326
|
+
Linux, macOS, and Windows with Node 24.19.0. It records npm lockfile integrity,
|
|
327
|
+
native executable hashes, the exact checkout commit, and toolchain versions.
|
|
328
|
+
Windows uses the existing x64 CI runner; upstream 0.0.2 has no Windows ARM64
|
|
329
|
+
package. The npm launcher gets a separate smoke check using its default libraries.
|
|
330
|
+
Parity and package consumers continue to use the official TypeScript libraries.
|
|
331
|
+
|
|
332
|
+
The campaign runs the compiler parity harness in default and single-threaded
|
|
333
|
+
modes, with two warmups and ten measured samples per arm. Complete builds use
|
|
334
|
+
the existing prepack owner with an explicit native compiler for both arms.
|
|
335
|
+
Ordinary builds retain the official compiler; this option is experimental
|
|
336
|
+
qualification tooling, not an adoption decision. Strict toolchain mode requires
|
|
337
|
+
the .NET helper build and cannot reuse the staged executable as a fallback.
|
|
338
|
+
|
|
339
|
+
Warm complete builds retain Rust/.NET outputs and recreate `dist`. Clean-output
|
|
340
|
+
builds use a fresh Cargo target and remove only the helper's `bin` and `obj`
|
|
341
|
+
directories before each invocation. Dependencies and their download stores stay
|
|
342
|
+
installed; OS and dependency caches are uncontrolled. These are **clean build
|
|
343
|
+
outputs**, not cold-machine or cold-page-cache measurements. Each arm has two
|
|
344
|
+
warmups, ten measured warm builds, and six measured clean-output builds, in
|
|
345
|
+
balanced ABBA/BAAB order. Timings cover the complete build, excluding hashing,
|
|
346
|
+
output cleanup and installed-package checks. They measure wall time only.
|
|
347
|
+
|
|
348
|
+
Every build must match all JavaScript, declaration, helper, and WASM bytes on
|
|
349
|
+
that runner. Cross-OS binary equality is not required. Source, build scripts,
|
|
350
|
+
lockfiles, generated helper input, compiler binaries, and libraries are checked
|
|
351
|
+
for mutation. Both arms in each cache mode get installed-package proof without
|
|
352
|
+
a rebuild, followed by another complete `dist` hash comparison.
|
|
353
|
+
|
|
354
|
+
The runner accepts only its disposable Actions checkout. The isolated job's
|
|
355
|
+
45-minute deadline owns cancellation of the full Cargo/.NET process tree;
|
|
356
|
+
it does not use a parent-only build timeout and then continue measuring.
|
|
357
|
+
It uploads at most three redacted JSON reports per platform, each capped at
|
|
358
|
+
4 MiB, for 14 days. Generated binaries and compiler installations stay temporary.
|
|
359
|
+
A failed campaign cannot establish a performance claim, even if it has samples.
|
|
360
|
+
|
|
361
|
+
OpenClaw is a separate qualification target. Its declaration build uses
|
|
362
|
+
`typescript/unstable` API transport and `program.emitToString`; a faster CLI
|
|
363
|
+
typecheck does not establish compatibility with that build owner. Reuse the
|
|
364
|
+
existing compiler lane's exact-source evidence before proposing an API/emission
|
|
365
|
+
adapter or a paired `build:strict-smoke` comparison.
|
|
366
|
+
|
|
367
|
+
### Separate scriptc runtime probe
|
|
368
|
+
|
|
369
|
+
Published scriptc 0.3.5 was installed on the same Linux host. Its native
|
|
370
|
+
control program built and ran after the host's missing Clang was installed.
|
|
371
|
+
The executable SHA-256 was
|
|
372
|
+
`b7087211a304c049de53fcaac40564e9a6e9adac6b4055c895ab340e82d47a84`.
|
|
373
|
+
Its [release tag](https://github.com/vercel-labs/scriptc/tree/973362d0d2be9b948b3e0d59d77ff3705278f24c)
|
|
374
|
+
is a source reference, not an attestation of the npm binary.
|
|
375
|
+
|
|
376
|
+
All three filesystem probes ran successfully in Node. With scriptc coverage
|
|
377
|
+
configured to fail on blockers:
|
|
378
|
+
|
|
379
|
+
- `statSync(path, { bigint: true })` failed preflight with `SC0001`: the
|
|
380
|
+
supported declaration accepts only one argument.
|
|
381
|
+
- Passing computed numeric open flags failed lowering with `SC2020`.
|
|
382
|
+
- An installed-package consumer using `root(path).readBytes(...)` failed
|
|
383
|
+
preflight with missing `BigIntStats` and `NodeJS.Platform` declarations
|
|
384
|
+
(`SC0001`), plus the package's computed/escaping `createRequire` use (`SC2013`).
|
|
385
|
+
|
|
386
|
+
These results agree with the release's documented
|
|
387
|
+
[descriptor/identity limits](https://github.com/vercel-labs/scriptc/blob/973362d0d2be9b948b3e0d59d77ff3705278f24c/docs/content/limitations/builtin-apis/filehandle-and-descriptor-i-o.mdx)
|
|
388
|
+
and [native-addon boundary](https://github.com/vercel-labs/scriptc/blob/973362d0d2be9b948b3e0d59d77ff3705278f24c/docs/content/limitations/native-addons.mdx).
|
|
389
|
+
They block runtime adoption; they do not establish a newly broken supported
|
|
390
|
+
contract or justify a fork. No fs-safe safety policy was weakened for these probes.
|
|
391
|
+
Further native-port design requires an exact-identity and addon/FFI solution
|
|
392
|
+
before broader runtime tests can become meaningful.
|