mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 18:14:29 -07:00
feat(policy): implement project-level policy support
Introduces a new 'Project' tier (Tier 3) for policies, allowing users to define project-specific rules in `$PROJECT_ROOT/.gemini/policies`. Key Changes: - **Core**: Added `PROJECT_POLICY_TIER` (3) and bumped `ADMIN_POLICY_TIER` to 4. Updated `getPolicyDirectories`, `getPolicyTier`, and `createPolicyEngineConfig` to handle project-level policy directories. - **Storage**: Added `getProjectPoliciesDir()` to the `Storage` class. - **CLI**: Updated `loadCliConfig` to securely load project policies. Crucially, project policies are **only loaded if the workspace is trusted**. - **Tests**: Added comprehensive tests for both core policy logic and CLI integration, verifying priority hierarchy (Admin > Project > User > Default) and trust checks. This hierarchy ensures that project-specific rules override user defaults but are still subject to system-wide admin enforcement.
This commit is contained in:
@@ -3198,6 +3198,8 @@ describe('Policy Engine Integration in loadCliConfig', () => {
|
||||
}),
|
||||
}),
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3219,6 +3221,8 @@ describe('Policy Engine Integration in loadCliConfig', () => {
|
||||
}),
|
||||
}),
|
||||
expect.anything(),
|
||||
undefined,
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
Config,
|
||||
applyAdminAllowlist,
|
||||
getAdminBlockedMcpServersMessage,
|
||||
Storage,
|
||||
type HookDefinition,
|
||||
type HookEventName,
|
||||
type OutputFormat,
|
||||
@@ -692,9 +693,15 @@ export async function loadCliConfig(
|
||||
policyPaths: argv.policy,
|
||||
};
|
||||
|
||||
let projectPoliciesDir: string | undefined;
|
||||
if (trustedFolder) {
|
||||
projectPoliciesDir = new Storage(cwd).getProjectPoliciesDir();
|
||||
}
|
||||
|
||||
const policyEngineConfig = await createPolicyEngineConfig(
|
||||
effectiveSettings,
|
||||
approvalMode,
|
||||
projectPoliciesDir,
|
||||
);
|
||||
policyEngineConfig.nonInteractive = !interactive;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { type Settings } from './settings.js';
|
||||
export async function createPolicyEngineConfig(
|
||||
settings: Settings,
|
||||
approvalMode: ApprovalMode,
|
||||
projectPoliciesDir?: string,
|
||||
): Promise<PolicyEngineConfig> {
|
||||
// Explicitly construct PolicySettings from Settings to ensure type safety
|
||||
// and avoid accidental leakage of other settings properties.
|
||||
@@ -28,7 +29,12 @@ export async function createPolicyEngineConfig(
|
||||
policyPaths: settings.policyPaths,
|
||||
};
|
||||
|
||||
return createCorePolicyEngineConfig(policySettings, approvalMode);
|
||||
return createCorePolicyEngineConfig(
|
||||
policySettings,
|
||||
approvalMode,
|
||||
undefined,
|
||||
projectPoliciesDir,
|
||||
);
|
||||
}
|
||||
|
||||
export function createPolicyUpdater(
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import * as path from 'node:path';
|
||||
import { loadCliConfig, type CliArgs } from './config.js';
|
||||
import { createTestMergedSettings } from './settings.js';
|
||||
import * as ServerConfig from '@google/gemini-cli-core';
|
||||
import { isWorkspaceTrusted } from './trustedFolders.js';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('./trustedFolders.js', () => ({
|
||||
isWorkspaceTrusted: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual<typeof ServerConfig>(
|
||||
'@google/gemini-cli-core',
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
loadServerHierarchicalMemory: vi.fn().mockResolvedValue({
|
||||
memoryContent: '',
|
||||
fileCount: 0,
|
||||
filePaths: [],
|
||||
}),
|
||||
createPolicyEngineConfig: vi.fn().mockResolvedValue({
|
||||
rules: [],
|
||||
checkers: [],
|
||||
}),
|
||||
getVersion: vi.fn().mockResolvedValue('test-version'),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Project-Level Policy CLI Integration', () => {
|
||||
const MOCK_CWD = process.cwd();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should have getProjectPoliciesDir on Storage class', () => {
|
||||
const storage = new ServerConfig.Storage(MOCK_CWD);
|
||||
expect(storage.getProjectPoliciesDir).toBeDefined();
|
||||
expect(typeof storage.getProjectPoliciesDir).toBe('function');
|
||||
});
|
||||
|
||||
it('should pass projectPoliciesDir to createPolicyEngineConfig when folder is trusted', async () => {
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: true,
|
||||
source: 'file',
|
||||
});
|
||||
|
||||
const settings = createTestMergedSettings();
|
||||
const argv = { query: 'test' } as unknown as CliArgs;
|
||||
|
||||
await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD });
|
||||
|
||||
// The wrapper createPolicyEngineConfig in policy.ts calls createCorePolicyEngineConfig
|
||||
// We check if the core one was called with 4 arguments, the 4th being the project dir
|
||||
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
undefined, // defaultPoliciesDir
|
||||
expect.stringContaining(path.join('.gemini', 'policies')),
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT pass projectPoliciesDir to createPolicyEngineConfig when folder is NOT trusted', async () => {
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
|
||||
const settings = createTestMergedSettings();
|
||||
const argv = { query: 'test' } as unknown as CliArgs;
|
||||
|
||||
await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD });
|
||||
|
||||
// The 4th argument (projectPoliciesDir) should be undefined
|
||||
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -136,6 +136,10 @@ export class Storage {
|
||||
return path.join(tempDir, identifier);
|
||||
}
|
||||
|
||||
getProjectPoliciesDir(): string {
|
||||
return path.join(this.getGeminiDir(), 'policies');
|
||||
}
|
||||
|
||||
ensureProjectTempDirExists(): void {
|
||||
fs.mkdirSync(this.getProjectTempDir(), { recursive: true });
|
||||
}
|
||||
|
||||
@@ -39,46 +39,54 @@ export const DEFAULT_CORE_POLICIES_DIR = path.join(__dirname, 'policies');
|
||||
// Policy tier constants for priority calculation
|
||||
export const DEFAULT_POLICY_TIER = 1;
|
||||
export const USER_POLICY_TIER = 2;
|
||||
export const ADMIN_POLICY_TIER = 3;
|
||||
export const PROJECT_POLICY_TIER = 3;
|
||||
export const ADMIN_POLICY_TIER = 4;
|
||||
|
||||
/**
|
||||
* Gets the list of directories to search for policy files, in order of decreasing priority
|
||||
* (Admin -> User -> Default).
|
||||
* Gets the list of directories to search for policy files, in order of increasing priority
|
||||
* (Default -> User -> Project -> Admin).
|
||||
*
|
||||
* @param defaultPoliciesDir Optional path to a directory containing default policies.
|
||||
* @param policyPaths Optional user-provided policy paths (from --policy flag).
|
||||
* When provided, these replace the default user policies directory.
|
||||
* @param projectPoliciesDir Optional path to a directory containing project policies.
|
||||
*/
|
||||
export function getPolicyDirectories(
|
||||
defaultPoliciesDir?: string,
|
||||
policyPaths?: string[],
|
||||
projectPoliciesDir?: string,
|
||||
): string[] {
|
||||
const dirs: string[] = [];
|
||||
const dirs = [];
|
||||
|
||||
// Default tier (lowest priority)
|
||||
dirs.push(defaultPoliciesDir ?? DEFAULT_CORE_POLICIES_DIR);
|
||||
// Admin tier (highest priority)
|
||||
dirs.push(Storage.getSystemPoliciesDir());
|
||||
|
||||
// User tier (middle priority)
|
||||
// User tier (second higheset priority)
|
||||
if (policyPaths && policyPaths.length > 0) {
|
||||
dirs.push(...policyPaths);
|
||||
} else {
|
||||
dirs.push(Storage.getUserPoliciesDir());
|
||||
}
|
||||
|
||||
// Project Tier (third highest)
|
||||
if (projectPoliciesDir) {
|
||||
dirs.push(projectPoliciesDir);
|
||||
}
|
||||
|
||||
// Admin tier (highest priority)
|
||||
dirs.push(Storage.getSystemPoliciesDir());
|
||||
// Default tier (lowest priority)
|
||||
dirs.push(defaultPoliciesDir ?? DEFAULT_CORE_POLICIES_DIR);
|
||||
|
||||
// Reverse so highest priority (Admin) is first
|
||||
return dirs.reverse();
|
||||
return dirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the policy tier (1=default, 2=user, 3=admin) for a given directory.
|
||||
* Determines the policy tier (1=default, 2=user, 3=project, 4=admin) for a given directory.
|
||||
* This is used by the TOML loader to assign priority bands.
|
||||
*/
|
||||
export function getPolicyTier(
|
||||
dir: string,
|
||||
defaultPoliciesDir?: string,
|
||||
projectPoliciesDir?: string,
|
||||
): number {
|
||||
const USER_POLICIES_DIR = Storage.getUserPoliciesDir();
|
||||
const ADMIN_POLICIES_DIR = Storage.getSystemPoliciesDir();
|
||||
@@ -99,6 +107,12 @@ export function getPolicyTier(
|
||||
if (normalizedDir === normalizedUser) {
|
||||
return USER_POLICY_TIER;
|
||||
}
|
||||
if (
|
||||
projectPoliciesDir &&
|
||||
normalizedDir === path.resolve(projectPoliciesDir)
|
||||
) {
|
||||
return PROJECT_POLICY_TIER;
|
||||
}
|
||||
if (normalizedDir === normalizedAdmin) {
|
||||
return ADMIN_POLICY_TIER;
|
||||
}
|
||||
@@ -153,12 +167,13 @@ export async function createPolicyEngineConfig(
|
||||
settings: PolicySettings,
|
||||
approvalMode: ApprovalMode,
|
||||
defaultPoliciesDir?: string,
|
||||
projectPoliciesDir?: string,
|
||||
): Promise<PolicyEngineConfig> {
|
||||
const policyDirs = getPolicyDirectories(
|
||||
defaultPoliciesDir,
|
||||
settings.policyPaths,
|
||||
projectPoliciesDir,
|
||||
);
|
||||
|
||||
const securePolicyDirs = await filterSecurePolicyDirectories(policyDirs);
|
||||
|
||||
const normalizedAdminPoliciesDir = path.resolve(
|
||||
@@ -171,7 +186,7 @@ export async function createPolicyEngineConfig(
|
||||
checkers: tomlCheckers,
|
||||
errors,
|
||||
} = await loadPoliciesFromToml(securePolicyDirs, (p) => {
|
||||
const tier = getPolicyTier(p, defaultPoliciesDir);
|
||||
const tier = getPolicyTier(p, defaultPoliciesDir, projectPoliciesDir);
|
||||
|
||||
// If it's a user-provided path that isn't already categorized as ADMIN,
|
||||
// treat it as USER tier.
|
||||
@@ -208,9 +223,10 @@ export async function createPolicyEngineConfig(
|
||||
// Priority bands (tiers):
|
||||
// - Default policies (TOML): 1 + priority/1000 (e.g., priority 100 → 1.100)
|
||||
// - User policies (TOML): 2 + priority/1000 (e.g., priority 100 → 2.100)
|
||||
// - Admin policies (TOML): 3 + priority/1000 (e.g., priority 100 → 3.100)
|
||||
// - Project policies (TOML): 3 + priority/1000 (e.g., priority 100 → 3.100)
|
||||
// - Admin policies (TOML): 4 + priority/1000 (e.g., priority 100 → 4.100)
|
||||
//
|
||||
// This ensures Admin > User > Default hierarchy is always preserved,
|
||||
// This ensures Admin > Project > User > Default hierarchy is always preserved,
|
||||
// while allowing user-specified priorities to work within each tier.
|
||||
//
|
||||
// Settings-based and dynamic rules (all in user tier 2.x):
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
||||
import nodePath from 'node:path';
|
||||
import { ApprovalMode } from './types.js';
|
||||
import { isDirectorySecure } from '../utils/security.js';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../utils/security.js', () => ({
|
||||
isDirectorySecure: vi.fn().mockResolvedValue({ secure: true }),
|
||||
}));
|
||||
|
||||
describe('Project-Level Policies', () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
const { Storage } = await import('../config/storage.js');
|
||||
vi.spyOn(Storage, 'getUserPoliciesDir').mockReturnValue(
|
||||
'/mock/user/policies',
|
||||
);
|
||||
vi.spyOn(Storage, 'getSystemPoliciesDir').mockReturnValue(
|
||||
'/mock/system/policies',
|
||||
);
|
||||
// Ensure security check always returns secure
|
||||
vi.mocked(isDirectorySecure).mockResolvedValue({ secure: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
vi.doUnmock('node:fs/promises');
|
||||
});
|
||||
|
||||
it('should load project policies with correct priority (Tier 3)', async () => {
|
||||
const projectPoliciesDir = '/mock/project/policies';
|
||||
const defaultPoliciesDir = '/mock/default/policies';
|
||||
|
||||
// Mock FS
|
||||
const actualFs =
|
||||
await vi.importActual<typeof import('node:fs/promises')>(
|
||||
'node:fs/promises',
|
||||
);
|
||||
|
||||
// Mock readdir to return a policy file for each tier
|
||||
const mockReaddir = vi.fn(async (path: string) => {
|
||||
const normalizedPath = nodePath.normalize(path);
|
||||
if (normalizedPath.includes('default'))
|
||||
return [
|
||||
{
|
||||
name: 'default.toml',
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
},
|
||||
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
|
||||
if (normalizedPath.includes('user'))
|
||||
return [
|
||||
{ name: 'user.toml', isFile: () => true, isDirectory: () => false },
|
||||
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
|
||||
if (normalizedPath.includes('project'))
|
||||
return [
|
||||
{
|
||||
name: 'project.toml',
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
},
|
||||
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
|
||||
if (normalizedPath.includes('system'))
|
||||
return [
|
||||
{ name: 'admin.toml', isFile: () => true, isDirectory: () => false },
|
||||
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
|
||||
return [];
|
||||
});
|
||||
|
||||
// Mock readFile to return content with distinct priorities/decisions
|
||||
const mockReadFile = vi.fn(async (path: string) => {
|
||||
if (path.includes('default.toml')) {
|
||||
return `[[rule]]
|
||||
toolName = "test_tool"
|
||||
decision = "allow"
|
||||
priority = 10
|
||||
`; // Tier 1 -> 1.010
|
||||
}
|
||||
if (path.includes('user.toml')) {
|
||||
return `[[rule]]
|
||||
toolName = "test_tool"
|
||||
decision = "deny"
|
||||
priority = 10
|
||||
`; // Tier 2 -> 2.010
|
||||
}
|
||||
if (path.includes('project.toml')) {
|
||||
return `[[rule]]
|
||||
toolName = "test_tool"
|
||||
decision = "allow"
|
||||
priority = 10
|
||||
`; // Tier 3 -> 3.010
|
||||
}
|
||||
if (path.includes('admin.toml')) {
|
||||
return `[[rule]]
|
||||
toolName = "test_tool"
|
||||
decision = "deny"
|
||||
priority = 10
|
||||
`; // Tier 4 -> 4.010
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
vi.doMock('node:fs/promises', () => ({
|
||||
...actualFs,
|
||||
default: { ...actualFs, readdir: mockReaddir, readFile: mockReadFile },
|
||||
readdir: mockReaddir,
|
||||
readFile: mockReadFile,
|
||||
}));
|
||||
|
||||
const { createPolicyEngineConfig } = await import('./config.js');
|
||||
|
||||
// Test 1: Project vs User (Project should win)
|
||||
const config = await createPolicyEngineConfig(
|
||||
{},
|
||||
ApprovalMode.DEFAULT,
|
||||
defaultPoliciesDir,
|
||||
projectPoliciesDir,
|
||||
);
|
||||
|
||||
const rules = config.rules?.filter((r) => r.toolName === 'test_tool');
|
||||
expect(rules).toBeDefined();
|
||||
|
||||
// Check for all 4 rules
|
||||
const defaultRule = rules?.find((r) => r.priority === 1.01);
|
||||
const userRule = rules?.find((r) => r.priority === 2.01);
|
||||
const projectRule = rules?.find((r) => r.priority === 3.01);
|
||||
const adminRule = rules?.find((r) => r.priority === 4.01);
|
||||
|
||||
expect(defaultRule).toBeDefined();
|
||||
expect(userRule).toBeDefined();
|
||||
expect(projectRule).toBeDefined();
|
||||
expect(adminRule).toBeDefined();
|
||||
|
||||
// Verify Hierarchy: Admin > Project > User > Default
|
||||
expect(adminRule!.priority).toBeGreaterThan(projectRule!.priority!);
|
||||
expect(projectRule!.priority).toBeGreaterThan(userRule!.priority!);
|
||||
expect(userRule!.priority).toBeGreaterThan(defaultRule!.priority!);
|
||||
});
|
||||
|
||||
it('should ignore project policies if projectPoliciesDir is undefined', async () => {
|
||||
const defaultPoliciesDir = '/mock/default/policies';
|
||||
|
||||
// Mock FS (simplified)
|
||||
const actualFs =
|
||||
await vi.importActual<typeof import('node:fs/promises')>(
|
||||
'node:fs/promises',
|
||||
);
|
||||
const mockReaddir = vi.fn(async (path: string) => {
|
||||
if (path.includes('default'))
|
||||
return [
|
||||
{
|
||||
name: 'default.toml',
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
},
|
||||
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
|
||||
return [];
|
||||
});
|
||||
const mockReadFile = vi.fn(
|
||||
async () => `[[rule]]
|
||||
toolName="t"
|
||||
decision="allow"
|
||||
priority=10`,
|
||||
);
|
||||
|
||||
vi.doMock('node:fs/promises', () => ({
|
||||
...actualFs,
|
||||
default: { ...actualFs, readdir: mockReaddir, readFile: mockReadFile },
|
||||
readdir: mockReaddir,
|
||||
readFile: mockReadFile,
|
||||
}));
|
||||
|
||||
const { createPolicyEngineConfig } = await import('./config.js');
|
||||
|
||||
const config = await createPolicyEngineConfig(
|
||||
{},
|
||||
ApprovalMode.DEFAULT,
|
||||
defaultPoliciesDir,
|
||||
undefined, // No project dir
|
||||
);
|
||||
|
||||
// Should only have default tier rule (1.01)
|
||||
const rules = config.rules;
|
||||
expect(rules).toHaveLength(1);
|
||||
expect(rules![0].priority).toBe(1.01);
|
||||
});
|
||||
|
||||
it('should load project policies and correctly transform to Tier 3', async () => {
|
||||
const projectPoliciesDir = '/mock/project/policies';
|
||||
|
||||
// Mock FS
|
||||
const actualFs =
|
||||
await vi.importActual<typeof import('node:fs/promises')>(
|
||||
'node:fs/promises',
|
||||
);
|
||||
const mockReaddir = vi.fn(async (path: string) => {
|
||||
if (path.includes('project'))
|
||||
return [
|
||||
{
|
||||
name: 'project.toml',
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
},
|
||||
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
|
||||
return [];
|
||||
});
|
||||
const mockReadFile = vi.fn(
|
||||
async () => `[[rule]]
|
||||
toolName="p_tool"
|
||||
decision="allow"
|
||||
priority=500`,
|
||||
);
|
||||
|
||||
vi.doMock('node:fs/promises', () => ({
|
||||
...actualFs,
|
||||
default: { ...actualFs, readdir: mockReaddir, readFile: mockReadFile },
|
||||
readdir: mockReaddir,
|
||||
readFile: mockReadFile,
|
||||
}));
|
||||
|
||||
const { createPolicyEngineConfig } = await import('./config.js');
|
||||
|
||||
const config = await createPolicyEngineConfig(
|
||||
{},
|
||||
ApprovalMode.DEFAULT,
|
||||
undefined,
|
||||
projectPoliciesDir,
|
||||
);
|
||||
|
||||
const rule = config.rules?.find((r) => r.toolName === 'p_tool');
|
||||
expect(rule).toBeDefined();
|
||||
// Project Tier (3) + 500/1000 = 3.5
|
||||
expect(rule?.priority).toBe(3.5);
|
||||
});
|
||||
});
|
||||
@@ -105,7 +105,7 @@ export type PolicyFileErrorType =
|
||||
export interface PolicyFileError {
|
||||
filePath: string;
|
||||
fileName: string;
|
||||
tier: 'default' | 'user' | 'admin';
|
||||
tier: 'default' | 'user' | 'project' | 'admin';
|
||||
ruleIndex?: number;
|
||||
errorType: PolicyFileErrorType;
|
||||
message: string;
|
||||
@@ -125,10 +125,11 @@ export interface PolicyLoadResult {
|
||||
/**
|
||||
* Converts a tier number to a human-readable tier name.
|
||||
*/
|
||||
function getTierName(tier: number): 'default' | 'user' | 'admin' {
|
||||
function getTierName(tier: number): 'default' | 'user' | 'project' | 'admin' {
|
||||
if (tier === 1) return 'default';
|
||||
if (tier === 2) return 'user';
|
||||
if (tier === 3) return 'admin';
|
||||
if (tier === 3) return 'project';
|
||||
if (tier === 4) return 'admin';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
@@ -211,7 +212,7 @@ function transformPriority(priority: number, tier: number): number {
|
||||
* 4. Collects detailed error information for any failures
|
||||
*
|
||||
* @param policyPaths Array of paths (directories or files) to scan for policy files
|
||||
* @param getPolicyTier Function to determine tier (1-3) for a path
|
||||
* @param getPolicyTier Function to determine tier (1-4) for a path
|
||||
* @returns Object containing successfully parsed rules and any errors encountered
|
||||
*/
|
||||
export async function loadPoliciesFromToml(
|
||||
|
||||
Reference in New Issue
Block a user