fix(remediation-followup): document the user-global baseline boundary (Refs #240)

#240.3 takes the "document the gap" fork deliberately: pinning the
machine-scoped global layer into the per-project hash baseline would
drift whenever the human edits global settings for another project.
The PINNED comment states the design (global writes stay guarded by
the human-only config-set gate), and a control-baseline test pins it:
snapshots cover .factory/ files only, and a write to the global layer
(hermetic XDG) leaves the snapshot equal and verification clean.

TDD: pinning the global file turns the boundary test red; policy 61/61
green after restore. No behavior change.

Refs #178

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
John Smith
2026-10-10 22:11:20 -04:00
co-authored by Claude Opus 5.5
parent c79812c03a
commit 41207c3466
2 changed files with 36 additions and 1 deletions
+8 -1
View File
@@ -87,7 +87,14 @@ export function controlClass(relative: string): ControlClass | undefined {
export const isControlPath = (relative: string) => controlClass(relative) !== undefined
/** Files whose hashes the baseline pins (existing, tracked control artifacts). */
/**
* Files whose hashes the baseline pins (existing, tracked control artifacts).
* Project-root only by design (#240.3): the user-global layer
* (globalConfigFile, under XDG_CONFIG_HOME) is machine-scoped and shared
* across projects — pinning it here would drift whenever the human edits
* global settings for another project. Global writes stay guarded by the
* human-only `es config set --global` gate instead.
*/
const PINNED = (root: string) => {
const layout = factoryLayout(root)
return [layout.gates, layout.frontier, layout.config]
+28
View File
@@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import path from "node:path"
import { globalConfigFile } from "../src/config.ts"
import {
controlClass,
evaluateRead,
@@ -10,6 +11,7 @@ import {
HUMAN_VERBS,
invokesHumanOnly,
rebaseline,
snapshotControl,
verifyControl,
} from "../src/trust/index.ts"
import { BOOLEAN_FLAGS, parseArgs } from "../src/util/args.ts"
@@ -686,6 +688,32 @@ describe("control baseline", () => {
expect((await verifyControl(root)).violations.join("\n")).toContain(".factory/config.json changed")
})
test("the user-global config sits outside the project baseline by design (#240.3)", async () => {
// The baseline pins project-root files only. The user-global layer
// (globalConfigFile, under XDG_CONFIG_HOME) is machine-scoped and
// shared across projects: pinning it here would drift whenever the
// human edits global settings for another project. Global writes stay
// guarded by the human-only `es config set --global` gate instead.
await rebaseline(root, "test")
const before = await snapshotControl(root)
expect(Object.keys(before).length).toBeGreaterThan(0)
expect(Object.keys(before).every((key) => key.startsWith(".factory/"))).toBe(true)
// A write to the global layer (hermetic: XDG pointed at the test state
// dir) changes nothing in the project baseline and verifies clean.
const realXdg = process.env.XDG_CONFIG_HOME
process.env.XDG_CONFIG_HOME = path.join(state, "xdg-config")
try {
const file = globalConfigFile()
await mkdir(path.dirname(file), { recursive: true })
await writeFile(file, '{"research":{"depth":3}}\n')
expect(await snapshotControl(root)).toEqual(before)
expect((await verifyControl(root)).clean).toBe(true)
} finally {
if (realXdg === undefined) delete process.env.XDG_CONFIG_HOME
else process.env.XDG_CONFIG_HOME = realXdg
}
})
test("drift in a waiver file is reported (#221)", async () => {
// Waiver records are pinned alongside the gates/config/frontier files, so
// a hand edit to a granted waiver halts the run like any other drift.