Audit Console Phase 1: tool_call_trace emission from mcplocal router,
session_bind/rbac_decision event kinds, GET /audit/sessions endpoint,
full Ink TUI with session sidebar, event timeline, and detail view
(mcpctl console --audit).
System prompts: move 6 hardcoded LLM prompts to mcpctl-system project
with extensible ResourceRuleRegistry validation framework, template
variable enforcement ({{maxTokens}}, {{pageCount}}), and delete-resets-
to-default behavior. All consumers fetch via SystemPromptFetcher with
hardcoded fallbacks.
CLI: -p shorthand for --project across get/create/delete/config commands,
console auto-scroll improvements, shell completions regenerated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { ResourceRuleRegistry } from '../src/validation/resource-rules.js';
|
|
import type { ResourceRule, RuleMeta, RuleContext, ValidationResult } from '../src/validation/resource-rules.js';
|
|
|
|
const noopCtx: RuleContext = {
|
|
findResource: async () => null,
|
|
};
|
|
|
|
function makeRule(
|
|
name: string,
|
|
resource: string,
|
|
matchFn: (data: unknown, meta: RuleMeta) => boolean,
|
|
validateFn: () => Promise<ValidationResult>,
|
|
): ResourceRule {
|
|
return { name, resource, match: matchFn, validate: validateFn };
|
|
}
|
|
|
|
describe('ResourceRuleRegistry', () => {
|
|
it('returns valid when no rules are registered', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('returns valid when no rules match the resource type', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
registry.register(makeRule('server-rule', 'server', () => true, async () => ({ valid: false, errors: ['fail'] })));
|
|
|
|
const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('skips rules that do not match the predicate', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
registry.register(makeRule('skip-rule', 'prompt', () => false, async () => ({ valid: false, errors: ['fail'] })));
|
|
|
|
const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('runs matching rules and returns errors', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
registry.register(makeRule('bad-rule', 'prompt', () => true, async () => ({
|
|
valid: false,
|
|
errors: ['missing template var'],
|
|
})));
|
|
|
|
const result = await registry.validate('prompt', { name: 'x' }, { operation: 'update' }, noopCtx);
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toEqual(['missing template var']);
|
|
});
|
|
|
|
it('aggregates errors from multiple matching rules', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
registry.register(makeRule('rule-1', 'prompt', () => true, async () => ({
|
|
valid: false,
|
|
errors: ['error A'],
|
|
})));
|
|
registry.register(makeRule('rule-2', 'prompt', () => true, async () => ({
|
|
valid: false,
|
|
errors: ['error B', 'error C'],
|
|
})));
|
|
|
|
const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toEqual(['error A', 'error B', 'error C']);
|
|
});
|
|
|
|
it('returns valid when all matching rules pass', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
registry.register(makeRule('ok-rule', 'prompt', () => true, async () => ({ valid: true })));
|
|
|
|
const result = await registry.validate('prompt', { name: 'x' }, { operation: 'update' }, noopCtx);
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('passes meta to match function', async () => {
|
|
const registry = new ResourceRuleRegistry();
|
|
let receivedMeta: RuleMeta | null = null;
|
|
registry.register(makeRule('meta-check', 'prompt', (_data, meta) => {
|
|
receivedMeta = meta;
|
|
return false;
|
|
}, async () => ({ valid: true })));
|
|
|
|
await registry.validate('prompt', { name: 'x' }, { operation: 'update', isSystemResource: true, projectName: 'sys' }, noopCtx);
|
|
expect(receivedMeta).toEqual({ operation: 'update', isSystemResource: true, projectName: 'sys' });
|
|
});
|
|
});
|