fix(#2665): stop watching shared ground, and derive the artifact prefix too
The previous commit widened the guard's watch set and claimed the enumeration was complete. Re-running the pre-push adversarial gate on that commit -- which I should have done before pushing it, and did not -- refuted the claim on four counts. All four were real. 1. FALSE POSITIVES, which is the worse polarity. `hooks/lib`, `hooks/package.json`, `scripts/lib` and `scripts/changeset` were watched WHOLESALE. The installer preserves foreign files in every one of them -- it removes the CommonJS marker only on an exact content match, because "a user-authored package.json is never deleted" -- so a user editing their own helper mid-suite tripped the guard. A driven probe produced four violations from touching only user-owned files. Watching shared ground is exactly what the module's SCOPE note refuses: a guard that cries wolf gets switched off, and then catches nothing at all. Now only exact GSD filenames inside those dirs are watched, and a test asserts foreign edits stay silent. 2. THE PREFIX WAS HARDCODED, which is this PR's own defect one level down. Each artifactLayout declares its OWN prefix, and kimi's `kimi-agents` layout declares `gsd` with no hyphen, writing `agents/gsd.yaml` and `agents/gsd.md`. A fixed `gsd-` scan is structurally blind to both, as it is to pi's `extensions/gsd.js`. The prefix is now derived per parent, as a SET -- the same destSubpath carries different prefixes across runtimes (`agents` appears with both `gsd` and `gsd-`). `extensions` joins the non-registry parents; pi declares no artifactLayout at all, so no registry walk could find it. 3. THE ENTRY BOUND FAILED OPEN on a non-finite limit: `Math.max(0, NaN)` is NaN, and every budget comparison against NaN is false, so the walk was unbounded -- the single thing the constant exists to prevent. Clamped with Number.isFinite. The walk also kept invoking itself for every remaining sibling after the budget was gone; it now returns. 4. THE RESIDUAL LIST WAS WRONG AGAIN. `agents/subagents/**` (kimi stages under an unprefixed intermediate dir), the loose capability generators, and the `extensions`/`plugins` CommonJS markers are all unwatched and were unnamed. They are named now, and the four shared dirs are recorded as DELIBERATELY not watched -- a different thing from missed. Each fix is negative-controlled and each control fires. The NaN control did not fire on its first form: the test asserted `truncated: false`, which the broken code also produces on a small tree, so it discriminated nothing. Repaired with a NaN perTarget against a small finite ceiling, where the two behaviours differ.
This commit is contained in:
@@ -246,32 +246,41 @@ describe('#2665: live-config hermeticity guard', () => {
|
||||
// NB: `workflows` is declared only in LOCAL scope (windsurf), so it is
|
||||
// deliberately NOT a global config-root parent -- the derivation walks
|
||||
// artifactLayout.global only.
|
||||
for (const p of ['agents', 'commands', 'command', 'skills', 'hooks', 'plugins', 'scripts']) {
|
||||
assert.ok(parents.includes(p), `expected derived parent ${p} in ${JSON.stringify(parents)}`);
|
||||
for (const p of ['agents', 'commands', 'command', 'skills', 'hooks', 'plugins', 'scripts', 'extensions']) {
|
||||
assert.ok(p in parents, `expected derived parent ${p} in ${JSON.stringify(Object.keys(parents))}`);
|
||||
}
|
||||
// kimi's kimi-agents layout declares prefix `gsd` (no hyphen); a fixed `gsd-`
|
||||
// scan cannot see agents/gsd.yaml, which is the defect this derivation closes.
|
||||
assert.ok(
|
||||
parents.agents.includes('gsd'),
|
||||
`agents must carry kimi's bare 'gsd' prefix; got ${JSON.stringify(parents.agents)}`,
|
||||
);
|
||||
assert.ok(owned.includes('skills/gsd'), `expected hermes skills/gsd in ${JSON.stringify(owned)}`);
|
||||
for (const o of ['hooks/lib', 'scripts/lib']) {
|
||||
// Exact GSD filenames only — never the shared directories that contain them.
|
||||
for (const o of ['scripts/fix-slash-commands.cjs', 'hooks/managed-hooks-registry.cjs']) {
|
||||
assert.ok(owned.includes(o), `expected owned nested ${o} in ${JSON.stringify(owned)}`);
|
||||
}
|
||||
for (const shared of ['hooks/lib', 'hooks/package.json', 'scripts/lib', 'scripts/changeset']) {
|
||||
assert.ok(!owned.includes(shared), `${shared} is shared ground and must NOT be watched wholesale`);
|
||||
}
|
||||
});
|
||||
|
||||
test('detects leaks the gsd- prefix rule structurally cannot see', () => {
|
||||
test('detects leaks a single hardcoded gsd- prefix cannot see', () => {
|
||||
const root = tmpRoot();
|
||||
try {
|
||||
for (const d of ['skills/gsd', 'hooks/lib', 'plugins', 'command', 'scripts/lib']) {
|
||||
for (const d of ['skills/gsd', 'agents', 'plugins', 'command', 'extensions']) {
|
||||
fs.mkdirSync(path.join(root, ...d.split('/')), { recursive: true });
|
||||
}
|
||||
const before = snapshotLiveConfig([root]);
|
||||
// Pre-existing dirs register as MODIFIED, so bump mtimes explicitly rather
|
||||
// than racing a coarse filesystem clock -- same reason the `modified` test
|
||||
// above uses utimesSync.
|
||||
const future = new Date(Date.now() + 10000);
|
||||
// kimi declares prefix `gsd` (no hyphen) and writes agents/gsd.yaml;
|
||||
// pi writes extensions/gsd.js. A fixed `gsd-` scan sees neither.
|
||||
const leaks = [
|
||||
['skills', 'gsd', 'executor.md'],
|
||||
['hooks', 'lib', 'git-cmd.js'],
|
||||
['agents', 'gsd.yaml'],
|
||||
['extensions', 'gsd.js'],
|
||||
['plugins', 'gsd-core.js'],
|
||||
['command', 'gsd-plan.md'],
|
||||
['scripts', 'lib', 'io.cjs'],
|
||||
];
|
||||
for (const seg of leaks) {
|
||||
const f = path.join(root, ...seg);
|
||||
@@ -281,7 +290,7 @@ describe('#2665: live-config hermeticity guard', () => {
|
||||
}
|
||||
|
||||
const hit = diffLiveConfig(before, snapshotLiveConfig([root])).map((v) => v.path);
|
||||
for (const expected of ['skills/gsd', 'hooks/lib', 'plugins/gsd-core.js', 'command/gsd-plan.md', 'scripts/lib']) {
|
||||
for (const expected of ['skills/gsd', 'agents/gsd.yaml', 'extensions/gsd.js', 'plugins/gsd-core.js', 'command/gsd-plan.md']) {
|
||||
const abs = path.join(root, ...expected.split('/'));
|
||||
assert.ok(hit.includes(abs), `${expected} leaked undetected; got ${JSON.stringify(hit)}`);
|
||||
}
|
||||
@@ -290,6 +299,42 @@ describe('#2665: live-config hermeticity guard', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('a user editing their OWN files in a shared dir is not a violation', () => {
|
||||
// The false-positive case a prior commit shipped: hooks/lib, hooks/package.json,
|
||||
// scripts/lib and scripts/changeset were watched WHOLESALE, so touching a
|
||||
// user-authored helper in any of them tripped the guard. The installer itself
|
||||
// preserves foreign files in all four, so they are not GSD's to watch.
|
||||
const root = tmpRoot();
|
||||
try {
|
||||
for (const d of ['hooks/lib', 'scripts/lib', 'scripts/changeset']) {
|
||||
fs.mkdirSync(path.join(root, ...d.split('/')), { recursive: true });
|
||||
}
|
||||
const foreign = [
|
||||
['hooks', 'package.json'],
|
||||
['hooks', 'lib', 'user-helper.js'],
|
||||
['scripts', 'lib', 'user-helper.cjs'],
|
||||
['scripts', 'changeset', 'user-tool.cjs'],
|
||||
];
|
||||
for (const seg of foreign) fs.writeFileSync(path.join(root, ...seg), 'mine');
|
||||
const before = snapshotLiveConfig([root]);
|
||||
const future = new Date(Date.now() + 10000);
|
||||
for (const seg of foreign) {
|
||||
const f = path.join(root, ...seg);
|
||||
fs.writeFileSync(f, 'mine, edited');
|
||||
fs.utimesSync(f, future, future);
|
||||
fs.utimesSync(path.dirname(f), future, future);
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(
|
||||
diffLiveConfig(before, snapshotLiveConfig([root])),
|
||||
[],
|
||||
'editing user-owned files in a shared dir must not trip the guard',
|
||||
);
|
||||
} finally {
|
||||
cleanup(root);
|
||||
}
|
||||
});
|
||||
|
||||
test('extra watch targets cover the fallback root as well as the ambient one', () => {
|
||||
// The MISSED finding from round 5's review: B3 closed this for the registry
|
||||
// roots and left the identical hole in resolveExtraWatchTargets.
|
||||
@@ -380,6 +425,26 @@ describe('#2665: live-config hermeticity guard', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('a non-finite injected limit falls back to the real bound, never fails open', () => {
|
||||
// Math.max(0, NaN) is NaN, and every budget comparison against NaN is false,
|
||||
// so the walk becomes unbounded — the one thing the bound exists to prevent.
|
||||
// The discriminator has to be a case where the two behaviours DIFFER: pair a
|
||||
// NaN perTarget with a small finite ceiling. Fixed, perTarget falls back to
|
||||
// MAX_ENTRIES and the ceiling still bites (truncated). Broken, min(NaN, 3) is
|
||||
// NaN and nothing truncates at all.
|
||||
const [dir] = treeWithEntries(6);
|
||||
try {
|
||||
const snap = snapshotLiveConfig([], [dir], { perTarget: NaN, total: 3 });
|
||||
assert.strictEqual(
|
||||
snap[path.resolve(dir)].truncated,
|
||||
true,
|
||||
'a NaN perTarget must fall back to a real bound, not disable budgeting',
|
||||
);
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test('the GLOBAL ceiling still bounds the aggregate, and reports unverified', () => {
|
||||
// The bound the single budget was really for is kept -- but when it engages,
|
||||
// the curtailed target is reported rather than silently attested clean.
|
||||
|
||||
Reference in New Issue
Block a user