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

  1. Give the rule a stable non-empty id and at least one entry in actions before upserting.
  2. Validate the rule schema at config load and report the file/key that produced the malformed rule.
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/9dcb97143228e5ed. Report an issue: GitHub.