test(guard): закрепить создание каталога как запись под read-only guard

ensureDir — такой же примитив записи lib/fs, как writeText и writeTextAtomic,
и guard обязан покрывать его наравне с ними. /var/lib/hy2xs, созданный до
успешной PHASE 0, уже делает хост изменённым: следующая чистая установка
опознает его содержимое как чужую установку.

Покрытие guard'ом всех примитивов ФС собрано в одном файле: непокрытый
примитив — это дыра в границе PHASE 0, и заметить её можно только там, где
проверяется весь набор.
This commit is contained in:
2026-08-30 18:29:54 +05:00
parent b22b4b0d99
commit df73459ea5
+14 -1
View File
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, test } from "bun:test";
import { classifyFailure } from "../src/commands/install";
import { disableReadOnlyGuard, enableReadOnlyGuard, isReadOnlyGuardActive } from "../src/lib/guard";
import { writeText, writeTextAtomic } from "../src/lib/fs";
import { ensureDir, writeText, writeTextAtomic } from "../src/lib/fs";
import {
runMutating,
runMutatingHidden,
@@ -40,6 +40,9 @@ describe("read-only guard (PHASE 0)", () => {
expect(isReadOnlyGuardActive()).toBe(false);
});
// Guard обязан покрывать ВСЕ примитивы записи lib/fs, а не только те, что
// существовали на момент его появления: непокрытый примитив — это дыра в
// границе PHASE 0, и заметить её можно только здесь.
test("под guard'ом запись в файл невозможна", async () => {
enableReadOnlyGuard("test phase");
await expect(writeText("/tmp/hy2xs-guard-probe", "x")).rejects.toThrow(/read-only guard violation/);
@@ -48,6 +51,16 @@ describe("read-only guard (PHASE 0)", () => {
).rejects.toThrow(/read-only guard violation/);
});
// Создание каталога — тоже запись. /var/lib/hy2xs, созданный до успешной
// PHASE 0, уже делает хост изменённым: следующая чистая установка опознает
// его содержимое как чужую установку.
test("под guard'ом создание каталога невозможно", async () => {
enableReadOnlyGuard("test phase");
await expect(ensureDir("/tmp/hy2xs-guard-probe-dir", { mode: 0o755 })).rejects.toThrow(
/read-only guard violation/
);
});
test("под guard'ом мутирующие раннеры недоступны", async () => {
enableReadOnlyGuard("test phase");
await expect(runMutating`true`).rejects.toThrow(/read-only guard violation/);