Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
115 lines
5.2 KiB
JavaScript
115 lines
5.2 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Preload fixture for #3709 review Blocker 2 — force `fs.unlinkSync` to throw
|
|
* EPERM inside the SPAWNED msd-context-monitor.js hook, so the PreCompact
|
|
* unlink-failure fallback branch is actually executed.
|
|
*
|
|
* `MSD_TEST_UNLINK_EPERM_MATCH` selects WHICH unlink fails: any path containing
|
|
* the substring throws EPERM; every other path unlinks for real. That lets one
|
|
* row fail only the warn sentinel (`-warned.json`) and another only the metrics
|
|
* bridge, so each half of the fallback is pinned separately.
|
|
*
|
|
* Loaded via `node --require <this file> hooks/msd-context-monitor.js` — the
|
|
* same seam as `shadow-report-throws-preload.cjs`, and for the same reasons:
|
|
* this is NOT a chmod/mode-bit trick (CONTRIBUTING.md: those no-op under root,
|
|
* so Docker/CI would pass the test with zero coverage), and NOT the in-process
|
|
* `withFaultyFs` seam (in-process-only — a spawned subprocess offers no shared
|
|
* memory to monkeypatch into). One-shot subprocess: no restoration needed.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
|
|
const realUnlinkSync = fs.unlinkSync;
|
|
const match = process.env.MSD_TEST_UNLINK_EPERM_MATCH || '';
|
|
|
|
if (match) {
|
|
fs.unlinkSync = function unlinkSyncWithInjectedEperm(p) {
|
|
if (String(p).includes(match)) {
|
|
const err = new Error(`EPERM: operation not permitted, unlink '${p}'`);
|
|
err.code = 'EPERM';
|
|
throw err;
|
|
}
|
|
return realUnlinkSync.apply(fs, arguments);
|
|
};
|
|
}
|
|
|
|
// `MSD_TEST_LSTAT_CLAIMS_FILE_MATCH`: make lstat CLAIM a regular file for
|
|
// matching paths — the lstat→open substitution-race shape (a symlink swapped in
|
|
// after the lstat), so the O_NOFOLLOW backstop is the guard actually under test
|
|
// (review of #3808, round 3, Minor 3). The real stat object is returned with
|
|
// only isFile overridden; everything else stays truthful.
|
|
const lstatMatch = process.env.MSD_TEST_LSTAT_CLAIMS_FILE_MATCH || '';
|
|
|
|
if (lstatMatch) {
|
|
const realLstatSync = fs.lstatSync;
|
|
fs.lstatSync = function lstatSyncClaimingFile(p) {
|
|
const st = realLstatSync.apply(fs, arguments);
|
|
if (String(p).includes(lstatMatch)) {
|
|
st.isFile = () => true;
|
|
// Engagement marker: without it, a match string that silently stops
|
|
// matching lets the REAL lstat refuse the symlink and every assertion
|
|
// in the substitution-race row still passes — without O_NOFOLLOW ever
|
|
// being the guard under test (Codex review of #3808, round 3). The row
|
|
// asserts this file exists.
|
|
try {
|
|
fs.writeFileSync(`${p}.msd-test-lstat-claimed`, '1');
|
|
} catch { /* marker is best-effort; the row will fail loudly without it */ }
|
|
}
|
|
return st;
|
|
};
|
|
}
|
|
|
|
// `MSD_TEST_SHRINK_AFTER_LSTAT_MATCH`: TRUNCATE a matching file immediately after
|
|
// `lstatSync` has measured it, so the subsequent `readSync` returns FEWER bytes than
|
|
// the size the guard allocated for. That is the shape round 11's Minor names — a
|
|
// concurrent legitimate writer truncating mid-write, independent of the planted-object
|
|
// case the rest of readSentinel guards — and it cannot be produced by ordinary file
|
|
// setup, because the shrink has to land inside the window between the two calls.
|
|
//
|
|
// Layered over the lstat wrapper above rather than folded into it: the two injections
|
|
// are selected by different env vars and a row may want either alone.
|
|
const shrinkMatch = process.env.MSD_TEST_SHRINK_AFTER_LSTAT_MATCH || '';
|
|
|
|
if (shrinkMatch) {
|
|
const beforeShrink = fs.lstatSync;
|
|
fs.lstatSync = function lstatSyncThenShrink(p) {
|
|
const st = beforeShrink.apply(fs, arguments);
|
|
if (String(p).includes(shrinkMatch) && st.isFile() && st.size > 1) {
|
|
// Truncate to a single byte: the read still succeeds, it just returns 1 instead
|
|
// of `st.size`, which is exactly the short read the guard must refuse.
|
|
//
|
|
// Engagement marker, same reason as the lstat-claim one above and learned the same way:
|
|
// the hook REWRITES this sentinel later in the same invocation, so a row that checked the
|
|
// file's size afterwards would see it back at full length and pass whether the truncation
|
|
// landed or not. The marker is the only durable evidence that this injection fired.
|
|
try {
|
|
fs.truncateSync(p, 1);
|
|
fs.writeFileSync(`${p}.msd-test-shrunk`, '1');
|
|
} catch { /* best effort — the row asserts the marker and fails loudly without it */ }
|
|
}
|
|
return st;
|
|
};
|
|
}
|
|
|
|
// `MSD_TEST_SHORT_WRITE_MATCH`: make `fs.writeSync` write only the FIRST BYTE for a
|
|
// matching fd's payload, once. A short write is permitted by the syscall and was
|
|
// previously discarded, so a truncated sentinel reached disk and every later read
|
|
// rejected it — the debounce accounting and the compaction watermark silently lost.
|
|
// The injection fires once so the retry loop under test can complete normally.
|
|
const shortWriteMatch = process.env.MSD_TEST_SHORT_WRITE_MATCH || '';
|
|
|
|
if (shortWriteMatch) {
|
|
const realWriteSync = fs.writeSync;
|
|
let fired = false;
|
|
fs.writeSync = function writeSyncShort(fd, buf, off, len) {
|
|
if (!fired && Buffer.isBuffer(buf) && len > 1) {
|
|
fired = true;
|
|
try { fs.writeFileSync(`${shortWriteMatch}.msd-test-short-write`, '1'); } catch { /* marker best effort */ }
|
|
return realWriteSync.call(fs, fd, buf, off, 1);
|
|
}
|
|
return realWriteSync.apply(fs, arguments);
|
|
};
|
|
}
|
|
|