@blamejs/core 0.9.14 → 0.9.16
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/CHANGELOG.md +2 -0
- package/lib/a2a-tasks.js +2 -2
- package/lib/a2a.js +11 -11
- package/lib/acme.js +5 -5
- package/lib/ai-input.js +2 -2
- package/lib/api-key.js +4 -4
- package/lib/api-snapshot.js +6 -6
- package/lib/app-shutdown.js +2 -2
- package/lib/app.js +5 -5
- package/lib/archive.js +8 -8
- package/lib/argon2-builtin.js +2 -2
- package/lib/atomic-file.js +53 -53
- package/lib/audit-sign.js +8 -8
- package/lib/audit-tools.js +22 -22
- package/lib/auth/dpop.js +3 -3
- package/lib/auth/elevation-grant.js +3 -3
- package/lib/auth/fido-mds3.js +6 -6
- package/lib/auth/jwt-external.js +2 -2
- package/lib/auth/sd-jwt-vc.js +2 -2
- package/lib/backup/bundle.js +17 -17
- package/lib/backup/index.js +36 -36
- package/lib/budr.js +3 -3
- package/lib/bundler.js +20 -20
- package/lib/circuit-breaker.js +4 -4
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/compliance.js +6 -7
- package/lib/config-drift.js +15 -15
- package/lib/config.js +6 -6
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +7 -7
- package/lib/crypto-field.js +9 -9
- package/lib/daemon.js +19 -19
- package/lib/db-file-lifecycle.js +24 -24
- package/lib/db-schema.js +2 -2
- package/lib/db.js +34 -34
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +17 -17
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-evaluation-context.js +2 -2
- package/lib/flag-providers.js +4 -4
- package/lib/gate-contract.js +5 -5
- package/lib/graphql-federation.js +4 -7
- package/lib/honeytoken.js +6 -6
- package/lib/http-client-cookie-jar.js +6 -6
- package/lib/http-client.js +18 -18
- package/lib/i18n.js +5 -5
- package/lib/keychain.js +5 -5
- package/lib/legal-hold.js +2 -2
- package/lib/local-db-thin.js +9 -9
- package/lib/log-stream-local.js +17 -17
- package/lib/log-stream-syslog.js +2 -2
- package/lib/log-stream.js +3 -3
- package/lib/log.js +2 -2
- package/lib/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +7 -7
- package/lib/mcp-tool-registry.js +6 -6
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +2 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +18 -18
- package/lib/middleware/compression.js +3 -3
- package/lib/middleware/csp-nonce.js +4 -4
- package/lib/middleware/health.js +7 -7
- package/lib/middleware/idempotency-key.js +163 -63
- package/lib/middleware/require-bound-key.js +4 -4
- package/lib/middleware/require-mtls.js +4 -4
- package/lib/migrations.js +5 -5
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-byte-quota.js +2 -2
- package/lib/network-dns.js +2 -2
- package/lib/network-nts.js +2 -2
- package/lib/network-proxy.js +3 -3
- package/lib/network-smtp-policy.js +2 -2
- package/lib/network-tls.js +17 -17
- package/lib/network.js +25 -25
- package/lib/notify.js +11 -11
- package/lib/object-store/gcs-bucket-ops.js +2 -2
- package/lib/object-store/gcs.js +5 -5
- package/lib/object-store/index.js +6 -6
- package/lib/object-store/local.js +19 -19
- package/lib/object-store/sigv4.js +3 -3
- package/lib/observability-tracer.js +4 -4
- package/lib/otel-export.js +3 -3
- package/lib/pagination.js +5 -5
- package/lib/parsers/safe-env.js +3 -3
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/pqc-gate.js +5 -5
- package/lib/pubsub-redis.js +2 -2
- package/lib/queue-local.js +3 -3
- package/lib/queue.js +2 -2
- package/lib/redis-client.js +4 -4
- package/lib/restore-bundle.js +17 -17
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +25 -25
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +6 -6
- package/lib/self-update.js +18 -18
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +9 -9
- package/lib/tls-exporter.js +5 -5
- package/lib/tracing.js +3 -3
- package/lib/vault/index.js +11 -11
- package/lib/vault/passphrase-ops.js +37 -37
- package/lib/vault/passphrase-source.js +2 -2
- package/lib/vault/rotate.js +64 -64
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/vault-aad.js +5 -5
- package/lib/watcher.js +22 -22
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +6 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/db.js
CHANGED
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* @card
|
|
42
42
|
* Database core — SQLite (node:sqlite) wrapped in encrypted-at-rest storage, sealed-column field-level crypto, append-only audit-chain integration, declarative schema reconcile, and run-once migrations.
|
|
43
43
|
*/
|
|
44
|
-
var
|
|
45
|
-
var
|
|
44
|
+
var nodeFs = require("fs");
|
|
45
|
+
var nodePath = require("path");
|
|
46
46
|
var { DatabaseSync } = require("node:sqlite");
|
|
47
47
|
var { Readable } = require("node:stream");
|
|
48
48
|
var atomicFile = require("./atomic-file");
|
|
@@ -92,7 +92,7 @@ var _dbErr = DbError.factory;
|
|
|
92
92
|
|
|
93
93
|
// Lazy: cluster-storage's _localDb pulls db back in, so eager require
|
|
94
94
|
// would deadlock the load order. cluster-storage is only used on the
|
|
95
|
-
// purge-audit-chain external-db
|
|
95
|
+
// purge-audit-chain external-db nodePath, which always runs after init.
|
|
96
96
|
var clusterStorage = lazyRequire(function () { return require("./cluster-storage"); });
|
|
97
97
|
|
|
98
98
|
// Lazy refs for the test-reset cascade. Each module requires db.js
|
|
@@ -639,7 +639,7 @@ function resolveTmpDir(optsTmpDir) {
|
|
|
639
639
|
if (optsTmpDir) return optsTmpDir;
|
|
640
640
|
var envTmp = safeEnv.readVar("BLAMEJS_TMPDIR");
|
|
641
641
|
if (envTmp) return envTmp;
|
|
642
|
-
if (
|
|
642
|
+
if (nodeFs.existsSync("/dev/shm")) return "/dev/shm";
|
|
643
643
|
return null;
|
|
644
644
|
}
|
|
645
645
|
|
|
@@ -650,8 +650,8 @@ function loadOrCreateDbKey(dataDirPath, keyPathOverride) {
|
|
|
650
650
|
// needs to live outside `dataDir` (e.g. a separate volume mounted
|
|
651
651
|
// from a KMS-fronted secret store). Default places it next to the
|
|
652
652
|
// encrypted DB so backup capture is one-tarball.
|
|
653
|
-
var keyPath = keyPathOverride ||
|
|
654
|
-
if (
|
|
653
|
+
var keyPath = keyPathOverride || nodePath.join(dataDirPath, "db.key.enc");
|
|
654
|
+
if (nodeFs.existsSync(keyPath)) {
|
|
655
655
|
var sealed = atomicFile.readSync(keyPath, { encoding: "utf8" }).trim();
|
|
656
656
|
var b64 = vault.unseal(sealed);
|
|
657
657
|
if (!b64) {
|
|
@@ -669,18 +669,18 @@ function loadOrCreateDbKey(dataDirPath, keyPathOverride) {
|
|
|
669
669
|
}
|
|
670
670
|
|
|
671
671
|
function decryptToTmp() {
|
|
672
|
-
if (!encPath || !
|
|
672
|
+
if (!encPath || !nodeFs.existsSync(encPath)) return;
|
|
673
673
|
// If a plaintext file already exists in tmpfs from a prior process, prefer
|
|
674
674
|
// the newer mtime (crash recovery — operator's most recent state wins).
|
|
675
|
-
if (
|
|
676
|
-
var plainStat =
|
|
677
|
-
var encStat =
|
|
675
|
+
if (nodeFs.existsSync(dbPath)) {
|
|
676
|
+
var plainStat = nodeFs.statSync(dbPath);
|
|
677
|
+
var encStat = nodeFs.statSync(encPath);
|
|
678
678
|
if (plainStat.mtimeMs > encStat.mtimeMs && plainStat.size > 0) {
|
|
679
679
|
log("plaintext is newer than encrypted — keeping plaintext (crash recovery)");
|
|
680
680
|
return;
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
|
-
var packed =
|
|
683
|
+
var packed = nodeFs.readFileSync(encPath);
|
|
684
684
|
if (packed.length < 26) return; // too short to be a valid envelope
|
|
685
685
|
// AAD binds the envelope to this deployment's data dir so two
|
|
686
686
|
// installs sharing the same operator passphrase can't swap each
|
|
@@ -703,8 +703,8 @@ function encryptToDisk() {
|
|
|
703
703
|
if (!encPath) return;
|
|
704
704
|
// Force WAL checkpoint so the .db file holds all committed transactions.
|
|
705
705
|
try { runSql(database, "PRAGMA wal_checkpoint(TRUNCATE)"); } catch (_e) { /* best effort */ }
|
|
706
|
-
if (!
|
|
707
|
-
atomicFile.writeSync(encPath, encryptPacked(
|
|
706
|
+
if (!nodeFs.existsSync(dbPath)) return;
|
|
707
|
+
atomicFile.writeSync(encPath, encryptPacked(nodeFs.readFileSync(dbPath), encKey, _dbEncAad(dataDir)));
|
|
708
708
|
}
|
|
709
709
|
|
|
710
710
|
/**
|
|
@@ -737,11 +737,11 @@ function snapshot() {
|
|
|
737
737
|
// so the snapshot reflects the current logical state, not just the
|
|
738
738
|
// pre-WAL pages.
|
|
739
739
|
try { runSql(database, "PRAGMA wal_checkpoint(TRUNCATE)"); } catch (_e) { /* best effort */ }
|
|
740
|
-
if (!
|
|
740
|
+
if (!nodeFs.existsSync(dbPath)) {
|
|
741
741
|
throw _dbErr("db/snapshot-no-source",
|
|
742
742
|
"snapshot: plaintext DB at " + dbPath + " is missing — did init complete?");
|
|
743
743
|
}
|
|
744
|
-
var plain =
|
|
744
|
+
var plain = nodeFs.readFileSync(dbPath);
|
|
745
745
|
if (!encPath || !encKey) {
|
|
746
746
|
// atRest: 'plain' — return the raw bytes. Operators wanting an
|
|
747
747
|
// encrypted snapshot under plain mode wrap with their own
|
|
@@ -756,9 +756,9 @@ function snapshot() {
|
|
|
756
756
|
// database.close().
|
|
757
757
|
function removePlaintextFiles() {
|
|
758
758
|
if (!dbPath) return;
|
|
759
|
-
try {
|
|
760
|
-
try {
|
|
761
|
-
try {
|
|
759
|
+
try { nodeFs.unlinkSync(dbPath); } catch (_e) { /* cleanup */ }
|
|
760
|
+
try { nodeFs.unlinkSync(dbPath + "-wal"); } catch (_e) { /* cleanup */ }
|
|
761
|
+
try { nodeFs.unlinkSync(dbPath + "-shm"); } catch (_e) { /* cleanup */ }
|
|
762
762
|
}
|
|
763
763
|
|
|
764
764
|
// Clean up stale plaintext DB files left by previously-crashed processes.
|
|
@@ -771,9 +771,9 @@ function cleanStaleTmpDbs(tmpDir) {
|
|
|
771
771
|
for (var i = 0; i < entries.length; i++) {
|
|
772
772
|
var full = entries[i].fullPath;
|
|
773
773
|
if (full === dbPath) continue;
|
|
774
|
-
try {
|
|
775
|
-
try {
|
|
776
|
-
try {
|
|
774
|
+
try { nodeFs.unlinkSync(full); } catch (_e) { /* concurrent cleanup */ }
|
|
775
|
+
try { nodeFs.unlinkSync(full + "-wal"); } catch (_e) { /* may not exist */ }
|
|
776
|
+
try { nodeFs.unlinkSync(full + "-shm"); } catch (_e) { /* may not exist */ }
|
|
777
777
|
}
|
|
778
778
|
}
|
|
779
779
|
|
|
@@ -865,7 +865,7 @@ async function init(opts) {
|
|
|
865
865
|
streamLimit = opts.streamLimit;
|
|
866
866
|
}
|
|
867
867
|
dataDir = opts.dataDir;
|
|
868
|
-
if (!
|
|
868
|
+
if (!nodeFs.existsSync(dataDir)) nodeFs.mkdirSync(dataDir, { recursive: true });
|
|
869
869
|
|
|
870
870
|
if (atRest === "encrypted") {
|
|
871
871
|
var tmpDir = resolveTmpDir(opts.tmpDir);
|
|
@@ -874,7 +874,7 @@ async function init(opts) {
|
|
|
874
874
|
"FATAL: atRest: 'encrypted' (default) requires tmpfs but none was found. " +
|
|
875
875
|
"Provide opts.tmpDir or set BLAMEJS_TMPDIR, or pass atRest: 'plain' (with warning).");
|
|
876
876
|
}
|
|
877
|
-
if (!
|
|
877
|
+
if (!nodeFs.existsSync(tmpDir)) nodeFs.mkdirSync(tmpDir, { recursive: true });
|
|
878
878
|
|
|
879
879
|
// D-H7 — if the resolved tmpDir is NOT actually tmpfs, the
|
|
880
880
|
// plaintext DB file lives on persistent storage. statvfs/statfs
|
|
@@ -884,7 +884,7 @@ async function init(opts) {
|
|
|
884
884
|
// out-of-band.
|
|
885
885
|
if (process.platform === "linux") {
|
|
886
886
|
var realTmp = "";
|
|
887
|
-
try { realTmp =
|
|
887
|
+
try { realTmp = nodeFs.realpathSync(tmpDir); } catch (_e) { /* stat best-effort */ }
|
|
888
888
|
if (realTmp.indexOf("/dev/shm") !== 0 && realTmp.indexOf("/run/shm") !== 0 &&
|
|
889
889
|
realTmp.indexOf("/run/user/") !== 0 && realTmp.indexOf("/tmp") !== 0) {
|
|
890
890
|
log.warn("WARNING: db.init: tmpDir '" + tmpDir + "' (real: '" + realTmp +
|
|
@@ -894,13 +894,13 @@ async function init(opts) {
|
|
|
894
894
|
}
|
|
895
895
|
}
|
|
896
896
|
|
|
897
|
-
// Operator overrides for the encrypted-DB on-disk
|
|
898
|
-
// takes a fully-qualified
|
|
897
|
+
// Operator overrides for the encrypted-DB on-disk nodePath. `opts.encryptedDbPath`
|
|
898
|
+
// takes a fully-qualified nodePath; `opts.encryptedDbName` overrides
|
|
899
899
|
// just the basename under `dataDir` (default "db.enc"). Helps when
|
|
900
900
|
// multiple framework-shaped instances share a dataDir.
|
|
901
901
|
encPath = opts.encryptedDbPath ||
|
|
902
|
-
|
|
903
|
-
dbPath =
|
|
902
|
+
nodePath.join(dataDir, opts.encryptedDbName || "db.enc");
|
|
903
|
+
dbPath = nodePath.join(tmpDir, "blamejs-" + generateToken(C.BYTES.bytes(16)) + ".db");
|
|
904
904
|
encKey = loadOrCreateDbKey(dataDir, opts.dbKeyPath);
|
|
905
905
|
|
|
906
906
|
cleanStaleTmpDbs(tmpDir);
|
|
@@ -910,7 +910,7 @@ async function init(opts) {
|
|
|
910
910
|
log.warn("WARNING: atRest: 'plain' — DB structure and row counts visible on disk.");
|
|
911
911
|
log.warn(" Field-level encryption (sealedFields) still protects sealed columns,");
|
|
912
912
|
log.warn(" but the simpler at-rest model is opt-out only. Default is 'encrypted'.");
|
|
913
|
-
dbPath =
|
|
913
|
+
dbPath = nodePath.join(dataDir, "blamejs.db");
|
|
914
914
|
encPath = null;
|
|
915
915
|
encKey = null;
|
|
916
916
|
}
|
|
@@ -1499,7 +1499,7 @@ function stream(sql) {
|
|
|
1499
1499
|
// review can reconstruct schema evolution from the chain alone (D-M1).
|
|
1500
1500
|
var DDL_RE = /^\s*(CREATE|DROP|ALTER|TRUNCATE|RENAME|ATTACH|DETACH|REINDEX)\b/i;
|
|
1501
1501
|
|
|
1502
|
-
// D-L7 — slow-query observability buckets for the local SQLite
|
|
1502
|
+
// D-L7 — slow-query observability buckets for the local SQLite nodePath.
|
|
1503
1503
|
// Highest matched bucket wins so the per-query emit is single-shot;
|
|
1504
1504
|
// operators dashboard on the `bucket` label.
|
|
1505
1505
|
var _SLOW_QUERY_BUCKETS_LOCAL = Object.freeze([
|
|
@@ -1907,7 +1907,7 @@ function exportCsv(opts) {
|
|
|
1907
1907
|
* cluster leader, re-encrypts the live tmpfs database back to
|
|
1908
1908
|
* `<dataDir>/db.enc`, closes the SQLite handle (releasing the file
|
|
1909
1909
|
* lock on Windows), then unlinks the plaintext sidecar files in
|
|
1910
|
-
*
|
|
1910
|
+
* tmpnodeFs. Safe to call multiple times — no-ops after the first
|
|
1911
1911
|
* successful close.
|
|
1912
1912
|
*
|
|
1913
1913
|
* @example
|
|
@@ -2327,8 +2327,8 @@ function eraseHard(tableName, rowId, opts) {
|
|
|
2327
2327
|
// Read the audit.tip sidecar file in dataDir and compare to the current
|
|
2328
2328
|
// audit_log MAX(monotonicCounter). Refuse boot on rollback (current < tip).
|
|
2329
2329
|
function _checkRollback(dataDirPath) {
|
|
2330
|
-
var tipPath =
|
|
2331
|
-
if (!
|
|
2330
|
+
var tipPath = nodePath.join(dataDirPath, "audit.tip");
|
|
2331
|
+
if (!nodeFs.existsSync(tipPath)) {
|
|
2332
2332
|
log("no audit.tip sidecar — skipping rollback check (first boot or operator-cleared)");
|
|
2333
2333
|
return;
|
|
2334
2334
|
}
|
|
@@ -3104,7 +3104,7 @@ module.exports = {
|
|
|
3104
3104
|
// Helper for audit.checkpoint to write the rollback-detection sidecar
|
|
3105
3105
|
_writeAuditTip: function (tip) {
|
|
3106
3106
|
if (!dataDir) return;
|
|
3107
|
-
var tipPath =
|
|
3107
|
+
var tipPath = nodePath.join(dataDir, "audit.tip");
|
|
3108
3108
|
atomicFile.writeSync(tipPath, JSON.stringify(tip, null, 2), { fileMode: 0o600 });
|
|
3109
3109
|
},
|
|
3110
3110
|
};
|
package/lib/dev.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* spawned app's logs unchanged.
|
|
12
12
|
*
|
|
13
13
|
* The hot-reload loop spawns the app as a child process, watches the
|
|
14
|
-
* source directories with `
|
|
14
|
+
* source directories with `nodeFs.watch({ recursive: true })`, and
|
|
15
15
|
* restarts the child when an unignored file changes. On-disk state
|
|
16
16
|
* (vault keys, encrypted DB, sealed cookies) survives the restart
|
|
17
17
|
* because the child re-opens the files; only in-process state is
|
|
@@ -41,18 +41,18 @@
|
|
|
41
41
|
*
|
|
42
42
|
* Test seams: `opts._spawn(cmd, args, sopts)` and
|
|
43
43
|
* `opts._watch(dir, wopts, listener)` default to `child_process.spawn`
|
|
44
|
-
* and `
|
|
44
|
+
* and `nodeFs.watch`; unit tests pass fakes to drive the engine without
|
|
45
45
|
* real subprocesses.
|
|
46
46
|
*
|
|
47
47
|
* @card
|
|
48
48
|
* Dev-mode helpers — hot-reload signal (file watch + child-process restart), route-list dump exposed via `dev.stats()`, and a request inspector courtesy of `stdio: 'inherit'` so the operator sees the spawned app's logs unchanged.
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
-
var
|
|
52
|
-
var
|
|
51
|
+
var nodePath = require("path");
|
|
52
|
+
var nodeFs = require("fs");
|
|
53
53
|
var lazyRequire = require("./lazy-require");
|
|
54
54
|
var logModule = require("./log");
|
|
55
|
-
var
|
|
55
|
+
var numericBounds = require("./numeric-bounds");
|
|
56
56
|
var safeEnv = require("./parsers/safe-env");
|
|
57
57
|
var validateOpts = require("./validate-opts");
|
|
58
58
|
var { FrameworkError } = require("./framework-error");
|
|
@@ -166,11 +166,11 @@ function create(opts) {
|
|
|
166
166
|
var ignore = Array.isArray(opts.ignore)
|
|
167
167
|
? DEFAULT_IGNORE.concat(opts.ignore)
|
|
168
168
|
: DEFAULT_IGNORE.slice();
|
|
169
|
-
|
|
169
|
+
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.graceMs,
|
|
170
170
|
"dev.create: opts.graceMs", DevError, "dev/bad-grace-ms");
|
|
171
171
|
var graceMs = opts.graceMs !== undefined ? opts.graceMs : DEFAULT_GRACE_MS;
|
|
172
172
|
var killSignal = typeof opts.killSignal === "string" ? opts.killSignal : DEFAULT_KILL_SIGNAL;
|
|
173
|
-
|
|
173
|
+
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.killTimeoutMs,
|
|
174
174
|
"dev.create: opts.killTimeoutMs", DevError, "dev/bad-kill-timeout-ms");
|
|
175
175
|
var killTimeoutMs = opts.killTimeoutMs !== undefined ? opts.killTimeoutMs : DEFAULT_KILL_TIMEOUT_MS;
|
|
176
176
|
var log = opts.log || null;
|
|
@@ -198,7 +198,7 @@ function create(opts) {
|
|
|
198
198
|
return childProcess().spawn(cmd, sargs, sopts);
|
|
199
199
|
};
|
|
200
200
|
var watchFn = opts._watch || function (dir, wopts, listener) {
|
|
201
|
-
return
|
|
201
|
+
return nodeFs.watch(dir, wopts, listener);
|
|
202
202
|
};
|
|
203
203
|
var setTimeoutFn = opts._setTimeout || setTimeout;
|
|
204
204
|
var clearTimeoutFn = opts._clearTimeout || clearTimeout;
|
|
@@ -313,7 +313,7 @@ function create(opts) {
|
|
|
313
313
|
function _onWatchEvent(dir, eventType, filename) {
|
|
314
314
|
if (!filename) return;
|
|
315
315
|
var rel = String(filename);
|
|
316
|
-
var full =
|
|
316
|
+
var full = nodePath.join(dir, rel);
|
|
317
317
|
if (_matchesAny(ignore, rel) || _matchesAny(ignore, full)) return;
|
|
318
318
|
_scheduleRestart(eventType + ":" + rel);
|
|
319
319
|
}
|
|
@@ -321,7 +321,7 @@ function create(opts) {
|
|
|
321
321
|
function _armWatchers() {
|
|
322
322
|
for (var i = 0; i < watch.length; i++) {
|
|
323
323
|
(function (dir) {
|
|
324
|
-
var resolved =
|
|
324
|
+
var resolved = nodePath.isAbsolute(dir) ? dir : nodePath.resolve(cwd, dir);
|
|
325
325
|
var w;
|
|
326
326
|
try {
|
|
327
327
|
w = watchFn(resolved, { recursive: true, persistent: false }, function (eventType, filename) {
|
package/lib/dr-runbook.js
CHANGED
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
* Disaster-recovery runbook executor — composes pre-recorded regulatory steps, operator confirmation gates, and the framework's audit chain into a posture-appropriate Markdown runbook a regulator can read alongside `b.audit`.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
var
|
|
46
|
-
var
|
|
45
|
+
var nodeFs = require("fs");
|
|
46
|
+
var nodePath = require("path");
|
|
47
47
|
var C = require("./constants");
|
|
48
48
|
var atomicFile = require("./atomic-file");
|
|
49
49
|
var lazyRequire = require("./lazy-require");
|
|
@@ -333,11 +333,11 @@ async function emit(opts) {
|
|
|
333
333
|
var body = sections.join("\n");
|
|
334
334
|
|
|
335
335
|
// Ensure outDir exists.
|
|
336
|
-
if (!
|
|
337
|
-
|
|
336
|
+
if (!nodeFs.existsSync(opts.outDir)) {
|
|
337
|
+
nodeFs.mkdirSync(opts.outDir, { recursive: true });
|
|
338
338
|
}
|
|
339
339
|
var filename = opts.filename || ("runbook-" + opts.posture + ".md");
|
|
340
|
-
var outPath =
|
|
340
|
+
var outPath = nodePath.join(opts.outDir, filename);
|
|
341
341
|
atomicFile.writeSync(outPath, body, { fileMode: 0o644 });
|
|
342
342
|
|
|
343
343
|
if (auditOn) {
|
package/lib/dual-control.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* M-of-N approval workflow for destructive operations (eraseHard, key rotation, etc.).
|
|
32
32
|
*/
|
|
33
33
|
var lazyRequire = require("./lazy-require");
|
|
34
|
-
var
|
|
34
|
+
var bCrypto = require("./crypto");
|
|
35
35
|
var requestHelpers = require("./request-helpers");
|
|
36
36
|
var validateOpts = require("./validate-opts");
|
|
37
37
|
var C = require("./constants");
|
|
@@ -261,7 +261,7 @@ function create(opts) {
|
|
|
261
261
|
if (reasonProblem) {
|
|
262
262
|
return Object.assign({ grantId: null }, reasonProblem);
|
|
263
263
|
}
|
|
264
|
-
var grantId = "dc-" +
|
|
264
|
+
var grantId = "dc-" + bCrypto.generateToken(C.BYTES.bytes(8));
|
|
265
265
|
var nowMs = Date.now();
|
|
266
266
|
var record = {
|
|
267
267
|
grantId: grantId,
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
* - externaldb.migrate.lock.acquired { holder }
|
|
49
49
|
* - externaldb.migrate.lock.released { holder }
|
|
50
50
|
*/
|
|
51
|
-
var
|
|
51
|
+
var nodePath = require("path");
|
|
52
52
|
var atomicFile = require("./atomic-file");
|
|
53
53
|
var canonicalJson = require("./canonical-json");
|
|
54
54
|
var { sha3Hash } = require("./crypto");
|
|
@@ -64,7 +64,7 @@ var ExternalDbMigrateError = defineClass("ExternalDbMigrateError", { alwaysPerma
|
|
|
64
64
|
|
|
65
65
|
// Lazy require — external-db imports back into this module via its
|
|
66
66
|
// public `migrate` namespace; load-order would cycle without lazy.
|
|
67
|
-
var
|
|
67
|
+
var externalDb = lazyRequire(function () { return require("./external-db"); });
|
|
68
68
|
|
|
69
69
|
var TRACKING_TABLE = "_blamejs_externaldb_migrations";
|
|
70
70
|
var LOCK_TABLE = "_blamejs_externaldb_migrations_lock";
|
|
@@ -285,7 +285,7 @@ function _list(dir) {
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
function _loadMigration(file, dir) {
|
|
288
|
-
var fullPath =
|
|
288
|
+
var fullPath = nodePath.join(dir, file);
|
|
289
289
|
// Drop the require cache so a test/dev that edits a file picks up the
|
|
290
290
|
// new content. Matches lib/migrations.js semantics.
|
|
291
291
|
try { delete require.cache[require.resolve(fullPath)]; } catch (_e) { /* not yet cached */ }
|
|
@@ -327,7 +327,7 @@ function _resolveBackendName(opts) {
|
|
|
327
327
|
}
|
|
328
328
|
// Default to the externalDb's defaultBackend; throw clear if not initialized.
|
|
329
329
|
var listed;
|
|
330
|
-
try { listed =
|
|
330
|
+
try { listed = externalDb().listBackends(); }
|
|
331
331
|
catch (_e) {
|
|
332
332
|
throw _err("externaldb-migrate/not-initialized",
|
|
333
333
|
"externalDb is not initialized — call b.externalDb.init({ backends }) first");
|
|
@@ -363,14 +363,14 @@ function create(opts) {
|
|
|
363
363
|
|
|
364
364
|
function _ctx(backendName) {
|
|
365
365
|
return {
|
|
366
|
-
externalDb:
|
|
366
|
+
externalDb: externalDb(),
|
|
367
367
|
backendName: backendName,
|
|
368
368
|
};
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
async function status() {
|
|
372
372
|
var backendName = _resolveBackendName(opts);
|
|
373
|
-
return await
|
|
373
|
+
return await externalDb().transaction(async function (xdb) {
|
|
374
374
|
await _ensureTrackingTable(xdb);
|
|
375
375
|
var res = await xdb.query(
|
|
376
376
|
"SELECT name, description, appliedAt FROM " + Q_TRACKING +
|
|
@@ -394,7 +394,7 @@ function create(opts) {
|
|
|
394
394
|
var backendName = _resolveBackendName(opts);
|
|
395
395
|
var ctx = _ctx(backendName);
|
|
396
396
|
|
|
397
|
-
return await
|
|
397
|
+
return await externalDb().transaction(async function (xdb) {
|
|
398
398
|
await _ensureTrackingTable(xdb);
|
|
399
399
|
await _ensureLockTable(xdb);
|
|
400
400
|
await _ensureHistoryTable(xdb);
|
|
@@ -404,7 +404,7 @@ function create(opts) {
|
|
|
404
404
|
// pool acquisition for the lock connection — the migrate runner
|
|
405
405
|
// serializes apply order, so this single-connection lock is
|
|
406
406
|
// sufficient.
|
|
407
|
-
var lockResult = await
|
|
407
|
+
var lockResult = await externalDb().transaction(async function (xdb) {
|
|
408
408
|
return await _acquireLock(xdb, opts);
|
|
409
409
|
}, { backend: backendName });
|
|
410
410
|
var lockHolder = lockResult.holder;
|
|
@@ -421,7 +421,7 @@ function create(opts) {
|
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
try {
|
|
424
|
-
var appliedRes = await
|
|
424
|
+
var appliedRes = await externalDb().query(
|
|
425
425
|
"SELECT name FROM " + Q_TRACKING, [], { backend: backendName }
|
|
426
426
|
);
|
|
427
427
|
var appliedSet = new Set(((appliedRes && appliedRes.rows) || []).map(function (r) { return r.name; }));
|
|
@@ -435,7 +435,7 @@ function create(opts) {
|
|
|
435
435
|
var mod = _loadMigration(file, dir);
|
|
436
436
|
var t0 = Date.now();
|
|
437
437
|
try {
|
|
438
|
-
await
|
|
438
|
+
await externalDb().transaction(async function (xdb) {
|
|
439
439
|
await mod.up(xdb, ctx);
|
|
440
440
|
var ranAt = new Date().toISOString();
|
|
441
441
|
await xdb.query(
|
|
@@ -489,7 +489,7 @@ function create(opts) {
|
|
|
489
489
|
return { applied: applied, skipped: skipped, backend: backendName };
|
|
490
490
|
} finally {
|
|
491
491
|
try {
|
|
492
|
-
await
|
|
492
|
+
await externalDb().transaction(async function (xdb) {
|
|
493
493
|
await _releaseLock(xdb, lockHolder);
|
|
494
494
|
}, { backend: backendName });
|
|
495
495
|
_emit(audit, "externaldb.migrate.lock.released", "success",
|
|
@@ -509,12 +509,12 @@ function create(opts) {
|
|
|
509
509
|
var backendName = _resolveBackendName(opts);
|
|
510
510
|
var ctx = _ctx(backendName);
|
|
511
511
|
|
|
512
|
-
await
|
|
512
|
+
await externalDb().transaction(async function (xdb) {
|
|
513
513
|
await _ensureTrackingTable(xdb);
|
|
514
514
|
await _ensureLockTable(xdb);
|
|
515
515
|
}, { backend: backendName });
|
|
516
516
|
|
|
517
|
-
var lockResultDown = await
|
|
517
|
+
var lockResultDown = await externalDb().transaction(async function (xdb) {
|
|
518
518
|
return await _acquireLock(xdb, opts);
|
|
519
519
|
}, { backend: backendName });
|
|
520
520
|
var lockHolder = lockResultDown.holder;
|
|
@@ -528,7 +528,7 @@ function create(opts) {
|
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
try {
|
|
531
|
-
var appliedRes = await
|
|
531
|
+
var appliedRes = await externalDb().query(
|
|
532
532
|
"SELECT name FROM " + Q_TRACKING + " ORDER BY appliedAt DESC, name DESC LIMIT $1",
|
|
533
533
|
[steps], { backend: backendName }
|
|
534
534
|
);
|
|
@@ -543,7 +543,7 @@ function create(opts) {
|
|
|
543
543
|
}
|
|
544
544
|
var t0 = Date.now();
|
|
545
545
|
try {
|
|
546
|
-
await
|
|
546
|
+
await externalDb().transaction(async function (xdb) {
|
|
547
547
|
await mod.down(xdb, ctx);
|
|
548
548
|
await xdb.query(
|
|
549
549
|
"DELETE FROM " + Q_TRACKING + " WHERE name = $1",
|
|
@@ -564,7 +564,7 @@ function create(opts) {
|
|
|
564
564
|
return { reverted: reverted, backend: backendName };
|
|
565
565
|
} finally {
|
|
566
566
|
try {
|
|
567
|
-
await
|
|
567
|
+
await externalDb().transaction(async function (xdb) {
|
|
568
568
|
await _releaseLock(xdb, lockHolder);
|
|
569
569
|
}, { backend: backendName });
|
|
570
570
|
_emit(audit, "externaldb.migrate.lock.released", "success",
|
|
@@ -588,7 +588,7 @@ function create(opts) {
|
|
|
588
588
|
async function history(historyOpts) {
|
|
589
589
|
historyOpts = historyOpts || {};
|
|
590
590
|
var backendName = _resolveBackendName(opts);
|
|
591
|
-
return await
|
|
591
|
+
return await externalDb().transaction(async function (xdb) {
|
|
592
592
|
await _ensureHistoryTable(xdb);
|
|
593
593
|
var res = await xdb.query(
|
|
594
594
|
"SELECT version, ranAt, ranBy, schemaIntrospectionHash, signature, publicKeyFingerprint " +
|
package/lib/external-db.js
CHANGED
|
@@ -754,8 +754,8 @@ async function transaction(fn, opts) {
|
|
|
754
754
|
if (isTransient && attempt <= maxRetries) {
|
|
755
755
|
_emitMetric("externaldb.transaction.retry", 1,
|
|
756
756
|
{ backend: b.name, code: txErr.code, attempt: String(attempt) });
|
|
757
|
-
var
|
|
758
|
-
var jitter =
|
|
757
|
+
var nodeCrypto = require("node:crypto");
|
|
758
|
+
var jitter = nodeCrypto.randomInt(0, 6); // allow:raw-byte-literal — 0-5ms jitter
|
|
759
759
|
await safeAsync.sleep(attempt * 5 + jitter); // allow:raw-time-literal — sub-second backoff
|
|
760
760
|
continue;
|
|
761
761
|
}
|
package/lib/fdx.js
CHANGED
|
@@ -85,7 +85,7 @@ var fapi2 = require("./fapi2");
|
|
|
85
85
|
var C = require("./constants");
|
|
86
86
|
var audit = require("./audit");
|
|
87
87
|
var validateOpts = require("./validate-opts");
|
|
88
|
-
var
|
|
88
|
+
var numericBounds = require("./numeric-bounds");
|
|
89
89
|
var { defineClass } = require("./framework-error");
|
|
90
90
|
var FdxError = defineClass("FdxError", { alwaysPermanent: true });
|
|
91
91
|
|
|
@@ -327,7 +327,7 @@ function consentReceipt(opts) {
|
|
|
327
327
|
throw FdxError.factory("BAD_SCOPES",
|
|
328
328
|
"fdx.consentReceipt: scopes must be a non-empty array");
|
|
329
329
|
}
|
|
330
|
-
|
|
330
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.durationMs,
|
|
331
331
|
"fdx.consentReceipt: durationMs", FdxError, "BAD_DURATION");
|
|
332
332
|
|
|
333
333
|
var issuedAt = Date.now();
|