mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 22:58:42 -10:00
feat(perms): OPR.0.4.8.2 rip out Claude config-file policy writes C1b/C1c/C2 (RED-first)
Founder-locked agnostic permission rip-out, seam 1. Rip-list = ASSESSMENT sha 5d450fdd. Base f81018fb.
OpenRig goes agnostic on permissions: it bakes NO config-file permission policy beyond the
usability floor. Two-surface rule — launch flags (stable) may be deterministic code; config-file
writes (chaotic) are removed.
- C1b/C1c: claude-settings.fragment.json stripped to ONLY permissions.defaultMode=acceptEdits
(+ enabledMcpjsonServers). The 13-entry allow-list and the rig up/down ask gates are gone.
- C2: provisionRigPermissions (the global Bash(rig:*) allow + _openrig_provenance writer into
~/.claude/settings.json) is DELETED — call, method, CONVENIENCE_BASELINE, and the now-dead
readStringArray. No retro-scrub: the new code never touches settings.json, so pre-existing
provenance-marked user files stay byte-identical. Trust/onboarding (C3/C4) plumbing kept.
- Floor kept byte-identical: fragment defaultMode acceptEdits + the --permission-mode acceptEdits
launch flag (unchanged).
Tests RED-first: agnostic-rip-out.test.ts (fragment has only defaultMode; fresh startup authors no
settings.json; no-retro-scrub byte-identical; launch-floor flag pin). Flipped the pins that
asserted the ripped rows (slice16-hygiene item 1, spec-library-starters, and removed the 7-test
provisionRigPermissions section with a rip-pointer). 201/201 across six suites; daemon tsc 0.
Guard-reviewed. VM-only, source-only (no live-host mutation); never pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
1 parent
2f71ca7520
commit
1c4e2db135
6 files changed
+142
-236
No files matched your search
@@ -1,25 +1,6 @@
|
||||
{
|
||||
"permissions": {
|
||||
"defaultMode": "acceptEdits",
|
||||
"allow": [
|
||||
"Bash(rig:*)",
|
||||
"Bash(git:*)",
|
||||
"Bash(npm:*)",
|
||||
"Bash(npx:*)",
|
||||
"Bash(pnpm:*)",
|
||||
"Bash(yarn:*)",
|
||||
"Bash(node:*)",
|
||||
"Bash(tsc:*)",
|
||||
"Bash(vitest:*)",
|
||||
"Bash(jest:*)",
|
||||
"Read",
|
||||
"Edit",
|
||||
"Write"
|
||||
],
|
||||
"ask": [
|
||||
"Bash(rig up:*)",
|
||||
"Bash(rig down:*)"
|
||||
]
|
||||
"defaultMode": "acceptEdits"
|
||||
},
|
||||
"enabledMcpjsonServers": [
|
||||
"exa",
|
||||
|
||||
@@ -583,58 +583,17 @@ export class ClaudeCodeAdapter implements RuntimeAdapter {
|
||||
}
|
||||
|
||||
private provisionManagedBootstrap(binding: { cwd?: string | null; tmuxSession?: string | null }): void {
|
||||
this.provisionRigPermissions();
|
||||
// OPR.0.4.8.2 agnostic rip-out: provisionRigPermissions (C2) removed — OpenRig no longer
|
||||
// authors any config-file permission policy. Trust/onboarding (C3/C4) are neutral plumbing, kept.
|
||||
this.provisionWorkspaceTrust(binding.cwd ?? null);
|
||||
this.provisionOnboardingState();
|
||||
}
|
||||
|
||||
/** Managed rig command baseline.
|
||||
* Additive only; never removes existing entries. Uses `Bash(cmd:*)`
|
||||
* colon-form per Claude Code convention.
|
||||
*
|
||||
* NOTE: this is not OpenRig's permission system. Harness-native
|
||||
* permissions should remain the primary control surface. */
|
||||
static readonly CONVENIENCE_BASELINE: readonly string[] = [
|
||||
"Bash(rig:*)",
|
||||
];
|
||||
|
||||
private provisionRigPermissions(): void {
|
||||
const home = this.fs.homedir ?? (typeof process !== "undefined" ? process.env.HOME : undefined);
|
||||
if (!home) return;
|
||||
|
||||
const settingsPath = nodePath.join(home, ".claude", "settings.json");
|
||||
this.fs.mkdirp(nodePath.dirname(settingsPath));
|
||||
|
||||
const settings = this.readJsonObject(settingsPath);
|
||||
const permissions = this.readJsonObjectField(settings, "permissions");
|
||||
const allow = new Set(this.readStringArray(permissions["allow"]));
|
||||
|
||||
// Bash convenience baseline — additive only; never removes existing entries.
|
||||
// Track whether any new patterns were actually added to avoid redundant writes
|
||||
// (preserves provenance timestamp + file content idempotency on re-runs).
|
||||
let added = 0;
|
||||
for (const pattern of ClaudeCodeAdapter.CONVENIENCE_BASELINE) {
|
||||
if (!allow.has(pattern)) {
|
||||
allow.add(pattern);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (added === 0) return; // All patterns already present; skip write
|
||||
|
||||
permissions["allow"] = Array.from(allow);
|
||||
settings["permissions"] = permissions;
|
||||
|
||||
// Provenance marker so operators can distinguish rig-injected from human-authored
|
||||
settings["_openrig_provenance"] = {
|
||||
author: "openrig-at-spawn",
|
||||
baseline: "convenience",
|
||||
patterns_added: added,
|
||||
ts: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
||||
}
|
||||
// OPR.0.4.8.2 agnostic rip-out: the CONVENIENCE_BASELINE (global `Bash(rig:*)` allow) and its
|
||||
// provisionRigPermissions writer (assessment row C2 — wrote into ~/.claude/settings.json with an
|
||||
// `_openrig_provenance` marker) are DELETED. OpenRig no longer authors any config-file permission
|
||||
// policy; the harness-native permission surface is the control surface. Existing provenance-marked
|
||||
// user files are NOT retro-scrubbed — the new code simply never touches settings.json.
|
||||
|
||||
private provisionWorkspaceTrust(cwd: string | null): void {
|
||||
if (!cwd) return;
|
||||
@@ -702,9 +661,7 @@ export class ClaudeCodeAdapter implements RuntimeAdapter {
|
||||
: {};
|
||||
}
|
||||
|
||||
private readStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
||||
}
|
||||
// OPR.0.4.8.2 rip-out: readStringArray removed — its only caller was provisionRigPermissions (C2).
|
||||
|
||||
/**
|
||||
* Best-effort: provision the OpenRig context collector for managed Claude sessions.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { ClaudeCodeAdapter, type ClaudeAdapterFsOps } from "../src/adapters/claude-code-adapter.js";
|
||||
import type { NodeBinding } from "../src/domain/runtime-adapter.js";
|
||||
import type { TmuxAdapter } from "../src/adapters/tmux.js";
|
||||
|
||||
// OPR.0.4.8.2 — agnostic permission RIP-OUT (founder-locked; rip-list = ASSESSMENT sha 5d450fdd).
|
||||
// Strips the three OpenRig-baked CONFIG-FILE policy writes beyond the floor: C1b (fragment
|
||||
// permissions.allow), C1c (fragment permissions.ask), C2 (provisionRigPermissions global allow).
|
||||
// KEEPS the usability floor byte-identical (fragment defaultMode=acceptEdits + the
|
||||
// --permission-mode acceptEdits launch flag). Two-surface rule: config-file writes go, launch-flag
|
||||
// floor stays. RED-first: the rip assertions FAIL on f81018fb (which ships allow/ask + rig allow).
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const FRAGMENT_PATH = path.join(__dirname, "../specs/agents/shared/runtime/claude-settings.fragment.json");
|
||||
|
||||
function mockTmux(): TmuxAdapter {
|
||||
return {
|
||||
sendText: vi.fn(async () => ({ ok: true as const })),
|
||||
hasSession: vi.fn(async () => true),
|
||||
getPaneCommand: vi.fn(async () => "claude"),
|
||||
capturePaneContent: vi.fn(async () => ""),
|
||||
createSession: vi.fn(async () => ({ ok: true as const })),
|
||||
killSession: vi.fn(async () => ({ ok: true as const })),
|
||||
listSessions: vi.fn(async () => []),
|
||||
listWindows: vi.fn(async () => []),
|
||||
listPanes: vi.fn(async () => []),
|
||||
sendKeys: vi.fn(async () => ({ ok: true as const })),
|
||||
} as unknown as TmuxAdapter;
|
||||
}
|
||||
|
||||
function mockFs(files?: Record<string, string>): ClaudeAdapterFsOps & { _store: Record<string, string> } {
|
||||
const store: Record<string, string> = { ...files };
|
||||
return {
|
||||
readFile: (p: string) => {
|
||||
if (p in store) return store[p]!;
|
||||
throw new Error(`Not found: ${p}`);
|
||||
},
|
||||
writeFile: (p: string, c: string) => {
|
||||
store[p] = c;
|
||||
},
|
||||
exists: (p: string) => p in store,
|
||||
mkdirp: () => {},
|
||||
copyFile: () => {},
|
||||
listFiles: (dir: string) => Object.keys(store).filter((k) => k.startsWith(dir + "/")).map((k) => k.slice(dir.length + 1)),
|
||||
_store: store,
|
||||
} as ClaudeAdapterFsOps & { _store: Record<string, string> };
|
||||
}
|
||||
|
||||
function makeBinding(cwd = "/project"): NodeBinding {
|
||||
return {
|
||||
id: "b1",
|
||||
nodeId: "n1",
|
||||
tmuxSession: "r01-impl",
|
||||
tmuxWindow: null,
|
||||
tmuxPane: null,
|
||||
cmuxWorkspace: null,
|
||||
cmuxSurface: null,
|
||||
updatedAt: "",
|
||||
cwd,
|
||||
};
|
||||
}
|
||||
|
||||
describe("OPR.0.4.8.2 agnostic rip-out — config-file policy writes stripped, launch-flag floor kept", () => {
|
||||
it("C1b/C1c: the shipped fragment carries NO permissions.allow / ask / deny", () => {
|
||||
const frag = JSON.parse(fs.readFileSync(FRAGMENT_PATH, "utf8"));
|
||||
expect(frag.permissions.allow).toBeUndefined();
|
||||
expect(frag.permissions.ask).toBeUndefined();
|
||||
expect(frag.permissions.deny).toBeUndefined();
|
||||
});
|
||||
|
||||
it("floor KEPT: fragment permissions.defaultMode is exactly acceptEdits (+ mcp servers untouched)", () => {
|
||||
const frag = JSON.parse(fs.readFileSync(FRAGMENT_PATH, "utf8"));
|
||||
expect(Object.keys(frag.permissions)).toEqual(["defaultMode"]); // ONLY the floor key remains
|
||||
expect(frag.permissions.defaultMode).toBe("acceptEdits");
|
||||
expect(frag.enabledMcpjsonServers).toEqual(["exa", "context7"]);
|
||||
});
|
||||
|
||||
it("C2: a fresh startup authors NO ~/.claude/settings.json for permissions", async () => {
|
||||
const fsm = mockFs({});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fsm, homedir: "/home/test" } });
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
// Post-rip: nothing authored solely for permissions. (Trust/onboarding write ~/.claude.json,
|
||||
// not settings.json.)
|
||||
expect(fsm._store["/home/test/.claude/settings.json"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("NO retro-scrub: a pre-existing provenance-marked settings.json is left BYTE-IDENTICAL", async () => {
|
||||
// NOTE: no Bash(rig:*) here — so the pre-rip code WOULD add it and rewrite (that is the RED).
|
||||
const existing = JSON.stringify(
|
||||
{ permissions: { allow: ["Bash(npm:*)"] }, _openrig_provenance: { author: "openrig-at-spawn", baseline: "convenience" } },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
const fsm = mockFs({ "/home/test/.claude/settings.json": existing });
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fsm, homedir: "/home/test" } });
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
expect(fsm._store["/home/test/.claude/settings.json"]).toBe(existing); // byte-identical, untouched
|
||||
});
|
||||
|
||||
it("floor launch flag KEPT byte-for-byte: launch command still contains --permission-mode acceptEdits", async () => {
|
||||
const tmux = mockTmux();
|
||||
const adapter = new ClaudeCodeAdapter({
|
||||
tmux,
|
||||
fsOps: mockFs(),
|
||||
sessionIdFactory: () => "11111111-1111-4111-8111-111111111111",
|
||||
});
|
||||
const result = await adapter.launchHarness(makeBinding(), { name: "dev-impl@test-rig" });
|
||||
expect(result.ok).toBe(true);
|
||||
const sendText = tmux.sendText as ReturnType<typeof vi.fn>;
|
||||
const cmd = sendText.mock.calls[0]?.[1] as string;
|
||||
expect(cmd).toContain("--permission-mode acceptEdits");
|
||||
});
|
||||
});
|
||||
@@ -590,139 +590,11 @@ describe("Claude Code runtime adapter", () => {
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
// --- Permission-config-at-spawn: Bash convenience baseline provisioning ---
|
||||
|
||||
it("provisionRigPermissions only adds the managed rig command on fresh settings", async () => {
|
||||
const fs = mockFs({});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const settings = JSON.parse(store["/home/test/.claude/settings.json"] ?? "{}");
|
||||
const allow: string[] = settings.permissions?.allow ?? [];
|
||||
|
||||
expect(allow).toContain("Bash(rig:*)");
|
||||
expect(allow).toEqual(["Bash(rig:*)"]);
|
||||
expect(allow).not.toContain("Bash(ls:*)");
|
||||
expect(allow).not.toContain("Bash(cat:*)");
|
||||
expect(allow).not.toContain("Bash(tail:*)");
|
||||
expect(allow).not.toContain("Bash(head:*)");
|
||||
expect(allow).not.toContain("Bash(wc:*)");
|
||||
expect(allow).not.toContain("Bash(grep:*)");
|
||||
expect(allow).not.toContain("Bash(rg:*)");
|
||||
expect(allow).not.toContain("Bash(pwd)");
|
||||
expect(allow).not.toContain("Bash(which:*)");
|
||||
expect(allow).not.toContain("Bash(echo:*)");
|
||||
});
|
||||
|
||||
it("provisionRigPermissions is additive — preserves existing allow entries", async () => {
|
||||
const fs = mockFs({
|
||||
"/home/test/.claude/settings.json": JSON.stringify({
|
||||
permissions: { allow: ["Bash(npm:*)", "Bash(git:*)"] },
|
||||
}),
|
||||
});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const settings = JSON.parse(store["/home/test/.claude/settings.json"] ?? "{}");
|
||||
const allow: string[] = settings.permissions?.allow ?? [];
|
||||
|
||||
// Pre-existing entries preserved
|
||||
expect(allow).toContain("Bash(npm:*)");
|
||||
expect(allow).toContain("Bash(git:*)");
|
||||
// Managed rig command added
|
||||
expect(allow).toContain("Bash(rig:*)");
|
||||
expect(allow).not.toContain("Bash(ls:*)");
|
||||
});
|
||||
|
||||
it("provisionRigPermissions is idempotent — no duplicates on re-run", async () => {
|
||||
const fs = mockFs({});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const settings = JSON.parse(store["/home/test/.claude/settings.json"] ?? "{}");
|
||||
const allow: string[] = settings.permissions?.allow ?? [];
|
||||
|
||||
const rigCount = allow.filter((p: string) => p === "Bash(rig:*)").length;
|
||||
expect(rigCount).toBe(1);
|
||||
expect(allow.filter((p: string) => p === "Bash(ls:*)")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("provisionRigPermissions is file-idempotent — identical settings.json on consecutive runs", async () => {
|
||||
const fs = mockFs({});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const firstWrite = store["/home/test/.claude/settings.json"];
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
const secondWrite = store["/home/test/.claude/settings.json"];
|
||||
|
||||
// Second run should not rewrite (all patterns already present);
|
||||
// file content stays byte-identical including provenance timestamp
|
||||
expect(secondWrite).toBe(firstWrite);
|
||||
});
|
||||
|
||||
it("provisionRigPermissions writes provenance marker", async () => {
|
||||
const fs = mockFs({});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const settings = JSON.parse(store["/home/test/.claude/settings.json"] ?? "{}");
|
||||
|
||||
expect(settings._openrig_provenance).toBeDefined();
|
||||
expect(settings._openrig_provenance.author).toBe("openrig-at-spawn");
|
||||
expect(settings._openrig_provenance.baseline).toBe("convenience");
|
||||
});
|
||||
|
||||
it("provisionRigPermissions preserves existing deny/ask entries", async () => {
|
||||
const fs = mockFs({
|
||||
"/home/test/.claude/settings.json": JSON.stringify({
|
||||
permissions: {
|
||||
deny: ["Bash(git push*)", "Bash(gh pr *)"],
|
||||
ask: ["Bash(git push:*)"],
|
||||
allow: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const settings = JSON.parse(store["/home/test/.claude/settings.json"] ?? "{}");
|
||||
|
||||
expect(settings.permissions.deny).toEqual(["Bash(git push*)", "Bash(gh pr *)"]);
|
||||
expect(settings.permissions.ask).toEqual(["Bash(git push:*)"]);
|
||||
});
|
||||
|
||||
it("provisionRigPermissions preserves non-permissions keys", async () => {
|
||||
const fs = mockFs({
|
||||
"/home/test/.claude/settings.json": JSON.stringify({
|
||||
theme: "dark",
|
||||
mcpServers: { context7: {} },
|
||||
permissions: { allow: [] },
|
||||
}),
|
||||
});
|
||||
const adapter = new ClaudeCodeAdapter({ tmux: mockTmux(), fsOps: { ...fs, homedir: "/home/test" } });
|
||||
|
||||
await adapter.deliverStartup([], makeBinding());
|
||||
|
||||
const store = (fs as unknown as { _store: Record<string, string> })._store;
|
||||
const settings = JSON.parse(store["/home/test/.claude/settings.json"] ?? "{}");
|
||||
|
||||
expect(settings.theme).toBe("dark");
|
||||
expect(settings.mcpServers).toEqual({ context7: {} });
|
||||
});
|
||||
// OPR.0.4.8.2 agnostic rip-out: the whole "Permission-config-at-spawn: Bash convenience
|
||||
// baseline provisioning" section (7 provisionRigPermissions tests) is REMOVED — the writer
|
||||
// (assessment C2) is deleted. Replacement coverage: agnostic-rip-out.test.ts asserts (a) a
|
||||
// fresh startup authors no ~/.claude/settings.json for permissions; (b) a pre-existing
|
||||
// provenance-marked settings file is left byte-identical (no retro-scrub).
|
||||
|
||||
// Pre-rip 'provisions project-local Claude hooks without clobbering
|
||||
// existing local settings or persisting the hook token' test removed in
|
||||
|
||||
@@ -11,19 +11,14 @@ import { fileURLToPath } from "node:url";
|
||||
const specs = fileURLToPath(new URL("../specs/", import.meta.url));
|
||||
|
||||
describe("Slice 16 — starter bootstrap hygiene", () => {
|
||||
it("item 1: shipped claude-settings fragment allows the dev toolchain; rig up/down stays gated", () => {
|
||||
it("item 1 (OPR.0.4.8.2 agnostic rip-out): fragment keeps ONLY the acceptEdits floor — no allow/ask/deny", () => {
|
||||
const frag = JSON.parse(readFileSync(specs + "agents/shared/runtime/claude-settings.fragment.json", "utf8"));
|
||||
const allow: string[] = frag.permissions.allow;
|
||||
// acceptEdits + the toolchain a TDD factory needs (git / npm test / tsc / node) + edit tools
|
||||
// The 13-entry allow-list (assessment C1b) and the rig up/down ask gates (C1c) are RIPPED —
|
||||
// OpenRig no longer bakes any config-file permission policy. The floor (acceptEdits) stays.
|
||||
expect(frag.permissions.defaultMode).toBe("acceptEdits");
|
||||
for (const perm of ["Bash(rig:*)", "Bash(git:*)", "Bash(npm:*)", "Bash(node:*)", "Bash(tsc:*)", "Read", "Edit", "Write"]) {
|
||||
expect(allow).toContain(perm);
|
||||
}
|
||||
// rig up/down remain behind the ask gate — NOT auto-allowed
|
||||
expect(frag.permissions.ask).toContain("Bash(rig up:*)");
|
||||
expect(frag.permissions.ask).toContain("Bash(rig down:*)");
|
||||
expect(allow).not.toContain("Bash(rig up:*)");
|
||||
expect(allow).not.toContain("Bash(rig down:*)");
|
||||
expect(frag.permissions.allow).toBeUndefined();
|
||||
expect(frag.permissions.ask).toBeUndefined();
|
||||
expect(frag.permissions.deny).toBeUndefined();
|
||||
});
|
||||
|
||||
it("item 3: the QA agent carries the test-driven-development skill it enforces", () => {
|
||||
|
||||
@@ -425,28 +425,13 @@ describe("Starter specs", () => {
|
||||
}
|
||||
const claudeSettingsResource = sharedRuntimeResources.find((entry) => entry.id === "claude-default-settings");
|
||||
const claudeSettings = JSON.parse(readFileSync(join(SPECS_ROOT, "agents/shared", claudeSettingsResource!.path), "utf-8"));
|
||||
// Slice 16 (item 1): the shipped fragment carries the dev toolchain a TDD
|
||||
// factory needs (git / npm / node / tsc + edit tools) so fresh product-team
|
||||
// seats don't stall on permission prompts. This assertion is the deliberate
|
||||
// security GATE — any FURTHER widening beyond this authorized set must update
|
||||
// it consciously. `rig up`/`rig down` stay behind the ask gate.
|
||||
expect(claudeSettings.permissions.allow).toEqual([
|
||||
"Bash(rig:*)",
|
||||
"Bash(git:*)",
|
||||
"Bash(npm:*)",
|
||||
"Bash(npx:*)",
|
||||
"Bash(pnpm:*)",
|
||||
"Bash(yarn:*)",
|
||||
"Bash(node:*)",
|
||||
"Bash(tsc:*)",
|
||||
"Bash(vitest:*)",
|
||||
"Bash(jest:*)",
|
||||
"Read",
|
||||
"Edit",
|
||||
"Write",
|
||||
]);
|
||||
expect(claudeSettings.permissions.ask).toEqual(["Bash(rig up:*)", "Bash(rig down:*)"]);
|
||||
// OPR.0.4.8.2 agnostic rip-out: the fragment's 13-entry allow-list (assessment C1b) and the
|
||||
// rig up/down ask gates (C1c) are RIPPED. OpenRig bakes NO config-file permission policy
|
||||
// beyond the acceptEdits floor — post-rip the fragment carries only defaultMode.
|
||||
expect(claudeSettings.permissions.allow).toBeUndefined();
|
||||
expect(claudeSettings.permissions.ask).toBeUndefined();
|
||||
expect(claudeSettings.permissions.deny).toBeUndefined();
|
||||
expect(claudeSettings.permissions.defaultMode).toBe("acceptEdits");
|
||||
|
||||
// Slice 29: claude-compact-in-place skill DELETED (was mis-imported as a
|
||||
// skill; its content remains in skill-spec docs not under skills/).
|
||||
|
||||
Reference in new issue
Block a user