fallow 2.81.0 → 2.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/fallow CHANGED
@@ -1,49 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execFileSync } = require('child_process');
4
- const { join } = require('path');
5
- const { existsSync } = require('fs');
6
- const { getPlatformPackage } = require('../scripts/platform-package');
7
-
8
- const pkg = (() => {
9
- if (process.platform !== 'linux') {
10
- return getPlatformPackage(process.platform, process.arch);
11
- }
12
-
13
- try {
14
- const { familySync } = require('detect-libc');
15
- return getPlatformPackage(process.platform, process.arch, familySync());
16
- } catch {
17
- // musl binaries are statically linked and work on both glibc and musl systems
18
- return getPlatformPackage(process.platform, process.arch, 'musl');
19
- }
20
- })();
21
-
22
- if (!pkg) {
23
- console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
24
- process.exit(1);
25
- }
26
-
27
- let binaryPath;
28
- try {
29
- const pkgDir = require.resolve(`${pkg}/package.json`);
30
- const binaryName = process.platform === 'win32' ? 'fallow.exe' : 'fallow';
31
- binaryPath = join(pkgDir, '..', binaryName);
32
- } catch {
33
- console.error(`Could not find ${pkg}. Run 'npm install' to install platform-specific binary.`);
34
- process.exit(1);
35
- }
36
-
37
- if (!existsSync(binaryPath)) {
38
- console.error(`Binary not found at ${binaryPath}`);
39
- process.exit(1);
40
- }
41
-
42
- try {
43
- execFileSync(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
44
- } catch (e) {
45
- if (e.status !== undefined) {
46
- process.exit(e.status);
47
- }
48
- throw e;
49
- }
3
+ const { runBinary } = require('../scripts/run-binary');
4
+ runBinary('fallow');
package/bin/fallow-lsp CHANGED
@@ -1,49 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execFileSync } = require('child_process');
4
- const { join } = require('path');
5
- const { existsSync } = require('fs');
6
- const { getPlatformPackage } = require('../scripts/platform-package');
7
-
8
- const pkg = (() => {
9
- if (process.platform !== 'linux') {
10
- return getPlatformPackage(process.platform, process.arch);
11
- }
12
-
13
- try {
14
- const { familySync } = require('detect-libc');
15
- return getPlatformPackage(process.platform, process.arch, familySync());
16
- } catch {
17
- // musl binaries are statically linked and work on both glibc and musl systems
18
- return getPlatformPackage(process.platform, process.arch, 'musl');
19
- }
20
- })();
21
-
22
- if (!pkg) {
23
- console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
24
- process.exit(1);
25
- }
26
-
27
- let binaryPath;
28
- try {
29
- const pkgDir = require.resolve(`${pkg}/package.json`);
30
- const binaryName = process.platform === 'win32' ? 'fallow-lsp.exe' : 'fallow-lsp';
31
- binaryPath = join(pkgDir, '..', binaryName);
32
- } catch {
33
- console.error(`Could not find ${pkg}. Run 'npm install' to install platform-specific binary.`);
34
- process.exit(1);
35
- }
36
-
37
- if (!existsSync(binaryPath)) {
38
- console.error(`Binary not found at ${binaryPath}`);
39
- process.exit(1);
40
- }
41
-
42
- try {
43
- execFileSync(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
44
- } catch (e) {
45
- if (e.status !== undefined) {
46
- process.exit(e.status);
47
- }
48
- throw e;
49
- }
3
+ const { runBinary } = require('../scripts/run-binary');
4
+ runBinary('fallow-lsp');
package/bin/fallow-mcp CHANGED
@@ -1,49 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execFileSync } = require('child_process');
4
- const { join } = require('path');
5
- const { existsSync } = require('fs');
6
- const { getPlatformPackage } = require('../scripts/platform-package');
7
-
8
- const pkg = (() => {
9
- if (process.platform !== 'linux') {
10
- return getPlatformPackage(process.platform, process.arch);
11
- }
12
-
13
- try {
14
- const { familySync } = require('detect-libc');
15
- return getPlatformPackage(process.platform, process.arch, familySync());
16
- } catch {
17
- // musl binaries are statically linked and work on both glibc and musl systems
18
- return getPlatformPackage(process.platform, process.arch, 'musl');
19
- }
20
- })();
21
-
22
- if (!pkg) {
23
- console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
24
- process.exit(1);
25
- }
26
-
27
- let binaryPath;
28
- try {
29
- const pkgDir = require.resolve(`${pkg}/package.json`);
30
- const binaryName = process.platform === 'win32' ? 'fallow-mcp.exe' : 'fallow-mcp';
31
- binaryPath = join(pkgDir, '..', binaryName);
32
- } catch {
33
- console.error(`Could not find ${pkg}. Run 'npm install' to install platform-specific binary.`);
34
- process.exit(1);
35
- }
36
-
37
- if (!existsSync(binaryPath)) {
38
- console.error(`Binary not found at ${binaryPath}`);
39
- process.exit(1);
40
- }
41
-
42
- try {
43
- execFileSync(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
44
- } catch (e) {
45
- if (e.status !== undefined) {
46
- process.exit(e.status);
47
- }
48
- throw e;
49
- }
3
+ const { runBinary } = require('../scripts/run-binary');
4
+ runBinary('fallow-mcp');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "description": "Deterministic codebase intelligence for TypeScript and JavaScript. Quality, risk, architecture, dependencies, duplication, and safe cleanup evidence for humans, CI, and agents. Optional runtime intelligence layer (Fallow Runtime) adds production execution evidence. Rust-native, sub-second, 96 framework plugins.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,9 +70,11 @@
70
70
  "schema.json"
71
71
  ],
72
72
  "scripts": {
73
- "postinstall": "node scripts/postinstall.js",
74
73
  "test:platform": "node --test scripts/platform-package.test.js",
75
- "test:verify": "node --test scripts/verify-binary.test.js"
74
+ "test:verify": "node --test scripts/verify-binary.test.js",
75
+ "test:sentinel-path": "node --test scripts/sentinel-path.test.js",
76
+ "test:lazy-verify": "node --test scripts/lazy-verify.test.js",
77
+ "test": "node --test scripts/*.test.js"
76
78
  },
77
79
  "dependencies": {
78
80
  "detect-libc": "2.1.2"
@@ -81,13 +83,13 @@
81
83
  "@tanstack/intent": "0.0.41"
82
84
  },
83
85
  "optionalDependencies": {
84
- "@fallow-cli/darwin-arm64": "2.81.0",
85
- "@fallow-cli/darwin-x64": "2.81.0",
86
- "@fallow-cli/linux-x64-gnu": "2.81.0",
87
- "@fallow-cli/linux-arm64-gnu": "2.81.0",
88
- "@fallow-cli/linux-x64-musl": "2.81.0",
89
- "@fallow-cli/linux-arm64-musl": "2.81.0",
90
- "@fallow-cli/win32-arm64-msvc": "2.81.0",
91
- "@fallow-cli/win32-x64-msvc": "2.81.0"
86
+ "@fallow-cli/darwin-arm64": "2.82.0",
87
+ "@fallow-cli/darwin-x64": "2.82.0",
88
+ "@fallow-cli/linux-x64-gnu": "2.82.0",
89
+ "@fallow-cli/linux-arm64-gnu": "2.82.0",
90
+ "@fallow-cli/linux-x64-musl": "2.82.0",
91
+ "@fallow-cli/linux-arm64-musl": "2.82.0",
92
+ "@fallow-cli/win32-arm64-msvc": "2.82.0",
93
+ "@fallow-cli/win32-x64-msvc": "2.82.0"
92
94
  }
93
95
  }
@@ -0,0 +1,351 @@
1
+ // Lazy first-run binary verification for the fallow npm wrapper.
2
+ //
3
+ // Called from bin/fallow, bin/fallow-lsp, and bin/fallow-mcp before each
4
+ // invocation execs the platform binary. Replaces the postinstall-time check
5
+ // that npm RFC 868 (npm/cli#9360) Phase 2 will silently disable for any
6
+ // consumer that has not added fallow to its `allowScripts` field.
7
+ //
8
+ // On every invocation:
9
+ // 1. Resolve a writable sentinel location (see sentinel-path.js).
10
+ // 2. If a valid sentinel exists, fast-path return ok (cache hit).
11
+ // 3. Otherwise run verifyInstalledSync (Ed25519 + SHA-256), write the
12
+ // sentinel on success, return result.
13
+ //
14
+ // Verification is fail-closed: any non-ok outcome surfaces to the caller and
15
+ // bin/fallow exits non-zero before execing the binary. FALLOW_SKIP_BINARY_VERIFY
16
+ // remains the documented escape hatch.
17
+ //
18
+ // Refs: SECURITY.md "Binary distribution and verification".
19
+ //
20
+ // No external deps beyond node:fs / node:path / node:crypto.
21
+
22
+ const fs = require("node:fs");
23
+ const path = require("node:path");
24
+ const crypto = require("node:crypto");
25
+
26
+ const { resolveSentinelPath } = require("./sentinel-path");
27
+ const { verifyInstalledSync, SKIP_ENV } = require("./verify-binary");
28
+
29
+ // Bumped to 2 when SHA-256 + platformPkgDir binding landed (closes the
30
+ // cross-install reuse gap in the shared $XDG fallback cache). v1 sentinels
31
+ // without these fields are invalidated automatically.
32
+ const SENTINEL_SCHEMA_VERSION = 2;
33
+ const VERIFY_LOG_ENV = "FALLOW_VERIFY_LOG";
34
+
35
+ // One-shot warning state: each warning class fires once per process,
36
+ // keyed by `code` so independent failure modes are still surfaced.
37
+ const _warningEmitted = new Set();
38
+
39
+ function warnOnce(code, message) {
40
+ if (_warningEmitted.has(code)) {
41
+ return;
42
+ }
43
+ _warningEmitted.add(code);
44
+ process.stderr.write(`fallow: ${message}\n`);
45
+ }
46
+
47
+ function isVerifyLogEnabled(env) {
48
+ const v = (env || process.env)[VERIFY_LOG_ENV];
49
+ if (typeof v !== "string") return false;
50
+ const lower = v.trim().toLowerCase();
51
+ return lower === "1" || lower === "true" || lower === "yes";
52
+ }
53
+
54
+ function emitVerifyLog(env, payload) {
55
+ if (!isVerifyLogEnabled(env)) return;
56
+ // Stable single-line format. Fields are space-separated key=value pairs.
57
+ // Order matters for log-grep ergonomics; do not reorder casually.
58
+ const parts = [];
59
+ for (const key of ["outcome", "cache", "sentinel", "reason", "code", "binary"]) {
60
+ if (payload[key] !== undefined && payload[key] !== null) {
61
+ const v = String(payload[key]).replace(/[\s"]/g, "_");
62
+ parts.push(`${key}=${v}`);
63
+ }
64
+ }
65
+ process.stderr.write(`fallow-verify ${parts.join(" ")}\n`);
66
+ }
67
+
68
+ function binaryTargetsForPlatform(platform) {
69
+ const ext = platform === "win32" ? ".exe" : "";
70
+ return [`fallow${ext}`, `fallow-lsp${ext}`, `fallow-mcp${ext}`];
71
+ }
72
+
73
+ function statMtimeMs(absPath) {
74
+ try {
75
+ return fs.statSync(absPath).mtimeMs;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function readManifestVersion(manifestPath) {
82
+ try {
83
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
84
+ if (typeof manifest.version === "string" && manifest.version.length > 0) {
85
+ return { version: manifest.version, name: manifest.name };
86
+ }
87
+ } catch {}
88
+ return null;
89
+ }
90
+
91
+ // Parse the sentinel JSON file, returning the parsed object or null on any
92
+ // failure (missing file, IO error, malformed JSON, non-object value).
93
+ function readSentinelFile(sentinelPath) {
94
+ if (typeof sentinelPath !== "string" || sentinelPath.length === 0) {
95
+ return null;
96
+ }
97
+ let raw;
98
+ try {
99
+ raw = fs.readFileSync(sentinelPath, "utf8");
100
+ } catch {
101
+ return null;
102
+ }
103
+ try {
104
+ const parsed = JSON.parse(raw);
105
+ return parsed && typeof parsed === "object" ? parsed : null;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ // True when the structural fields (schema, version, name, binaries map,
112
+ // install identity) all match. Mtime + bytes checks are in separate helpers
113
+ // to keep each function flat.
114
+ //
115
+ // `platformPkgDir` binds the sentinel to a specific install location. This
116
+ // matters when the sentinel lives in the shared $XDG fallback cache: two
117
+ // installs of the same package + version on the same host would otherwise
118
+ // share a sentinel keyed only by name/version, and an attacker who can write
119
+ // to one install's binary could ride the other install's sentinel state.
120
+ function sentinelStructureMatches(parsed, manifest, platformPkgDir) {
121
+ if (!parsed) return false;
122
+ if (parsed.schemaVersion !== SENTINEL_SCHEMA_VERSION) return false;
123
+ if (parsed.packageVersion !== manifest.version) return false;
124
+ if (parsed.packageName !== manifest.name) return false;
125
+ if (parsed.platformPkgDir !== platformPkgDir) return false;
126
+ return parsed.binaries && typeof parsed.binaries === "object";
127
+ }
128
+
129
+ // Compute a hex SHA-256 of a file's bytes. Used to bind the sentinel to the
130
+ // exact binary bytes that passed verification, so a tampered binary with the
131
+ // same mtime cannot ride a stale cache entry.
132
+ function sha256OfFile(absPath) {
133
+ try {
134
+ return crypto.createHash("sha256").update(fs.readFileSync(absPath)).digest("hex");
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ // True when every recorded binary still matches the on-disk state. The mtime
141
+ // check is a cheap pre-filter; the SHA-256 check is the load-bearing
142
+ // integrity gate that defends against same-mtime cross-install reuse where a
143
+ // tampered binary happens to land with the recorded mtime.
144
+ function sentinelBinariesMatch(parsed, platformPkgDir, platform) {
145
+ for (const target of binaryTargetsForPlatform(platform)) {
146
+ const recorded = parsed.binaries[target];
147
+ if (!recorded || typeof recorded.mtimeMs !== "number") return false;
148
+ if (typeof recorded.sha256 !== "string" || recorded.sha256.length !== 64) return false;
149
+ const binaryPath = path.join(platformPkgDir, target);
150
+ const current = statMtimeMs(binaryPath);
151
+ if (current === null) return false;
152
+ // 1ms tolerance covers NFS / FAT mtime rounding.
153
+ if (Math.abs(current - recorded.mtimeMs) > 1) return false;
154
+ const sha = sha256OfFile(binaryPath);
155
+ if (sha !== recorded.sha256) return false;
156
+ }
157
+ return true;
158
+ }
159
+
160
+ // Read and validate the sentinel. Returns true when the sentinel is valid for
161
+ // the current platform package state (binary mtimes + bytes + version +
162
+ // package name + install identity all match). Any failure mode (missing file,
163
+ // malformed JSON, schema drift, stale mtime, mismatched bytes / name /
164
+ // version, install-dir mismatch) returns false, triggering re-verify.
165
+ function isSentinelValid(sentinelPath, platformPkgDir, manifest, platform) {
166
+ const parsed = readSentinelFile(sentinelPath);
167
+ if (!sentinelStructureMatches(parsed, manifest, platformPkgDir)) return false;
168
+ return sentinelBinariesMatch(parsed, platformPkgDir, platform);
169
+ }
170
+
171
+ function buildSentinelPayload(platformPkgDir, manifest, platform) {
172
+ const binaries = {};
173
+ for (const target of binaryTargetsForPlatform(platform)) {
174
+ const binaryPath = path.join(platformPkgDir, target);
175
+ const mtimeMs = statMtimeMs(binaryPath);
176
+ const sha256 = sha256OfFile(binaryPath);
177
+ if (mtimeMs === null || sha256 === null) {
178
+ return null;
179
+ }
180
+ binaries[target] = { mtimeMs, sha256 };
181
+ }
182
+ return {
183
+ schemaVersion: SENTINEL_SCHEMA_VERSION,
184
+ verifiedAt: new Date().toISOString(),
185
+ packageVersion: manifest.version,
186
+ packageName: manifest.name,
187
+ platformPkgDir,
188
+ binaries,
189
+ };
190
+ }
191
+
192
+ // Write the sentinel via tmp + rename for atomicity. On POSIX the rename is
193
+ // atomic; on Windows the worst case is last-writer-wins under concurrency,
194
+ // and both writers produce structurally identical payloads (only `verifiedAt`
195
+ // differs, which is informational).
196
+ function writeSentinel(sentinelPath, payload) {
197
+ const dir = path.dirname(sentinelPath);
198
+ const tmpName = `${path.basename(sentinelPath)}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
199
+ const tmpPath = path.join(dir, tmpName);
200
+ try {
201
+ fs.writeFileSync(tmpPath, JSON.stringify(payload), { flag: "wx" });
202
+ fs.renameSync(tmpPath, sentinelPath);
203
+ return { ok: true };
204
+ } catch (err) {
205
+ // Best-effort cleanup; ignore unlink errors.
206
+ try {
207
+ fs.unlinkSync(tmpPath);
208
+ } catch {}
209
+ return { ok: false, code: err.code || "unknown", message: err.message };
210
+ }
211
+ }
212
+
213
+ function isSkipRequested(env) {
214
+ const v = (env || process.env)[SKIP_ENV];
215
+ return v === "1" || v === "true" || v === "yes";
216
+ }
217
+
218
+ // Main entry point. Synchronous by design: bin/fallow runs this before
219
+ // execFileSync, so the verify result must be available without awaiting.
220
+ //
221
+ // Required input:
222
+ // platformPkgDir: absolute path to the @fallow-cli/<platform> directory
223
+ // packageName: the platform package name (used as sentinel filename in
224
+ // the cache-dir fallback locations)
225
+ // manifestPath: absolute path to the platform package's package.json
226
+ // (read for version + name)
227
+ //
228
+ // Optional input (all dependency-injected for tests):
229
+ // verifyFn - replaces verifyBinaryAt (sig check) in verifyInstalledSync
230
+ // digestProvider - sync function returning a sha256 digest string; used when
231
+ // fallowDigests is missing from the manifest (tests only;
232
+ // production install path always has fallowDigests)
233
+ // env - process.env (defaults to process.env)
234
+ // platform - process.platform (defaults to process.platform)
235
+ // logger - function (line: string) -> void (defaults to stderr)
236
+ //
237
+ // Returns one of:
238
+ // { ok: true, cached: true, sentinelPath }
239
+ // { ok: true, cached: false, sentinelPath: string|null }
240
+ // { ok: true, skipped: true, reason }
241
+ // { ok: false, code, message, binary?, package? }
242
+ function buildVerifyOptions(input, manifest) {
243
+ const opts = {
244
+ dirOverride: input.platformPkgDir,
245
+ version: manifest.version,
246
+ platformId: (input.packageName || "").replace(/^@fallow-cli\//, "") || "unknown",
247
+ };
248
+ if (typeof input.verifyFn === "function") opts.verifyFn = input.verifyFn;
249
+ if (typeof input.digestProvider === "function") opts.digestProvider = input.digestProvider;
250
+ return opts;
251
+ }
252
+
253
+ // Persist the sentinel on a successful verify. Logs (warn-once) when the
254
+ // resolved cache location is read-only or every cascade step failed.
255
+ function persistSentinel(sentinel, platformPkgDir, manifest, platform) {
256
+ if (!sentinel.path) {
257
+ warnOnce(
258
+ "sentinel-no-writable-location",
259
+ `no writable cache location for verify sentinel (platform pkg dir read-only, ` +
260
+ `$FALLOW_VERIFY_CACHE_DIR unset, $XDG_CACHE_HOME / %LOCALAPPDATA% unavailable). ` +
261
+ `Binary verification will re-run on every invocation. Set ${SKIP_ENV}=1 to ` +
262
+ `bypass verification entirely.`,
263
+ );
264
+ return;
265
+ }
266
+ const payload = buildSentinelPayload(platformPkgDir, manifest, platform);
267
+ if (!payload) return;
268
+ const write = writeSentinel(sentinel.path, payload);
269
+ if (write.ok) return;
270
+ warnOnce(
271
+ "sentinel-write-failed",
272
+ `could not persist verify sentinel at ${sentinel.path} (${write.code}): ` +
273
+ `verification will re-run on next invocation. Set FALLOW_VERIFY_CACHE_DIR ` +
274
+ `to a writable location to enable caching.`,
275
+ );
276
+ }
277
+
278
+ function ensureVerified(input) {
279
+ const {
280
+ platformPkgDir,
281
+ packageName,
282
+ manifestPath,
283
+ env = process.env,
284
+ platform = process.platform,
285
+ } = input || {};
286
+
287
+ if (isSkipRequested(env)) {
288
+ const reason = `${SKIP_ENV} is set`;
289
+ // Warn once per process so the bypass stays visible in CI logs and
290
+ // vendor audits regardless of whether the user runs `--version` or
291
+ // sets FALLOW_VERIFY_LOG. Documented in SECURITY.md.
292
+ warnOnce(
293
+ "skip-binary-verify-set",
294
+ `${SKIP_ENV} is set; binary verification is skipped. ` +
295
+ `Unset the variable to re-enable Ed25519 + SHA-256 verification. ` +
296
+ `See SECURITY.md for the trust model.`,
297
+ );
298
+ emitVerifyLog(env, { outcome: "skipped", reason });
299
+ return { ok: true, skipped: true, reason };
300
+ }
301
+
302
+ if (typeof platformPkgDir !== "string" || platformPkgDir.length === 0) {
303
+ return { ok: false, code: "platform-package-missing", message: "platformPkgDir is required" };
304
+ }
305
+
306
+ const manifest = manifestPath ? readManifestVersion(manifestPath) : null;
307
+ if (!manifest) {
308
+ return {
309
+ ok: false,
310
+ code: "manifest-invalid",
311
+ message: `cannot read platform package manifest at ${manifestPath}`,
312
+ };
313
+ }
314
+
315
+ const sentinel = resolveSentinelPath({ platformPkgDir, packageName, env, platform });
316
+
317
+ // Cache hit: sentinel exists, schema matches, mtimes match, version matches.
318
+ if (sentinel.path && isSentinelValid(sentinel.path, platformPkgDir, manifest, platform)) {
319
+ emitVerifyLog(env, { outcome: "ok", cache: "hit", sentinel: sentinel.path });
320
+ return { ok: true, cached: true, sentinelPath: sentinel.path };
321
+ }
322
+
323
+ // Cache miss: run full sig + digest verification.
324
+ const result = verifyInstalledSync(buildVerifyOptions(input || {}, manifest));
325
+
326
+ if (!result.ok) {
327
+ emitVerifyLog(env, {
328
+ outcome: "fail",
329
+ cache: "miss",
330
+ code: result.code,
331
+ binary: result.binary,
332
+ });
333
+ return { ...result, package: packageName };
334
+ }
335
+
336
+ persistSentinel(sentinel, platformPkgDir, manifest, platform);
337
+ emitVerifyLog(env, { outcome: "ok", cache: "miss", sentinel: sentinel.path || "<none>" });
338
+ return { ok: true, cached: false, sentinelPath: sentinel.path };
339
+ }
340
+
341
+ // Reset the warn-once memo. Test-only.
342
+ function _resetWarningState() {
343
+ _warningEmitted.clear();
344
+ }
345
+
346
+ module.exports = {
347
+ ensureVerified,
348
+ SENTINEL_SCHEMA_VERSION,
349
+ VERIFY_LOG_ENV,
350
+ _resetWarningState,
351
+ };