ruvnet/ruflo · error · Error
invalid-policy-rule
Error message
invalid-policy-rule
What it means
PolicyEngine.upsertRule() requires every rule to carry a truthy id and a non-empty actions array before it will clone and store it. A rule missing either is rejected with Error('invalid-policy-rule') to keep malformed rules out of evaluated state.
Source
Thrown at v3/@claude-flow/security/src/policy/engine.ts:79
return engine;
}
exportState(): PolicyState {
return structuredClone(this.state);
}
setMode(mode: PolicyState['mode']): void {
this.state.mode = mode;
}
setConfiguredMode(mode: PolicyState['mode']): void {
this.state.configuredMode = mode;
const rank = { legacy: 0, observe: 1, enforce: 2 } as const;
if (rank[mode] > rank[this.state.mode]) this.state.mode = mode;
}
upsertRule(rule: PolicyRule): void {
if (!rule.id || !rule.actions.length) throw new Error('invalid-policy-rule');
for (const value of [
rule.constraints?.maxCostUsd,
rule.constraints?.maxTokens,
rule.constraints?.maxConcurrency,
]) {
if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
throw new Error('invalid-policy-rule-limit');
}
}
const index = this.state.rules.findIndex((item) => item.id === rule.id);
if (index >= 0) this.state.rules[index] = structuredClone(rule);
else this.state.rules.push(structuredClone(rule));
}
setBudget(limit: BudgetLimit): void {
if (limit.periodMs <= 0) throw new Error('invalid-budget-period');
if (!Number.isFinite(limit.periodMs)
|| [limit.maxCostUsd, limit.maxTokens].some((value) => (View on GitHub (pinned to fa13ee4ad6)
Solutions
- Give the rule a stable non-empty id and at least one entry in actions before upserting.
- Validate the rule schema at config load and report the file/key that produced the malformed rule.
- Skip-and-log empty rules during bulk import instead of pushing them into the engine.
Example fix
// before
engine.upsertRule(loadedRule); // loadedRule.actions is undefined
// after
if (!loadedRule.id || !loadedRule.actions?.length) {
throw new Error(`malformed rule in ${file}: id and at least one action required`);
}
engine.upsertRule(loadedRule); Defensive patterns
Strategy: type-guard
Validate before calling
for (const rule of rules) {
if (!rule.id || !rule.actions?.length) {
throw new Error(`malformed rule (id=${rule.id}): needs id and >=1 action`);
}
} Type guard
function isUpsertableRule(r: unknown): r is PolicyRule {
const rule = r as PolicyRule;
return typeof rule?.id === 'string' && rule.id.length > 0 &&
Array.isArray(rule.actions) && rule.actions.length > 0;
} Try / catch
try {
engine.upsertRule(rule);
} catch (err) {
if (err instanceof Error && err.message === 'invalid-policy-rule') {
return reportConfigError(`rule ${JSON.stringify(rule)} lacks id/actions`);
}
throw err;
} Prevention
- Validate rule files at load time and name the offending file/entry, not just the engine error.
- Add a JSON schema for policy rule documents so editors catch missing keys before runtime.
- Skip-and-log empty-action rules during bulk imports instead of feeding them to the engine.
When it happens
Trigger: engine.upsertRule({ id: '', actions: ['read'] }); upsertRule({ id: 'r1', actions: [] }) after a filter/map step removed all actions; rules deserialized from YAML where the actions key was mistyped or omitted.
Common situations: Loading policy files written by hand where 'action:' singular was used instead of 'actions:'; codegen that emits rules with conditionally-empty action lists; trimming rules for a test environment that strips actions.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- mode must be legacy, observe, or enforce
- Policy canonicalization rejects non-finite numbers
- invalid-policy-rule-limit
- invalid-budget-period
- invalid-budget-limit
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/9dcb97143228e5ed.
Report an issue: GitHub.