{"record":{"id":"9dcb97143228e5ed","repo":"ruvnet/ruflo","slug":"invalid-policy-rule","errorCode":null,"errorMessage":"invalid-policy-rule","messagePattern":"invalid-policy-rule","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/policy/engine.ts","lineNumber":79,"sourceCode":"    return engine;\n  }\n\n  exportState(): PolicyState {\n    return structuredClone(this.state);\n  }\n\n  setMode(mode: PolicyState['mode']): void {\n    this.state.mode = mode;\n  }\n\n  setConfiguredMode(mode: PolicyState['mode']): void {\n    this.state.configuredMode = mode;\n    const rank = { legacy: 0, observe: 1, enforce: 2 } as const;\n    if (rank[mode] > rank[this.state.mode]) this.state.mode = mode;\n  }\n\n  upsertRule(rule: PolicyRule): void {\n    if (!rule.id || !rule.actions.length) throw new Error('invalid-policy-rule');\n    for (const value of [\n      rule.constraints?.maxCostUsd,\n      rule.constraints?.maxTokens,\n      rule.constraints?.maxConcurrency,\n    ]) {\n      if (value !== undefined && (!Number.isFinite(value) || value < 0)) {\n        throw new Error('invalid-policy-rule-limit');\n      }\n    }\n    const index = this.state.rules.findIndex((item) => item.id === rule.id);\n    if (index >= 0) this.state.rules[index] = structuredClone(rule);\n    else this.state.rules.push(structuredClone(rule));\n  }\n\n  setBudget(limit: BudgetLimit): void {\n    if (limit.periodMs <= 0) throw new Error('invalid-budget-period');\n    if (!Number.isFinite(limit.periodMs)\n      || [limit.maxCostUsd, limit.maxTokens].some((value) => (","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/engine.ts#L61-L97","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nengine.upsertRule(loadedRule); // loadedRule.actions is undefined\n\n// after\nif (!loadedRule.id || !loadedRule.actions?.length) {\n  throw new Error(`malformed rule in ${file}: id and at least one action required`);\n}\nengine.upsertRule(loadedRule);","handlingStrategy":"type-guard","validationCode":"for (const rule of rules) {\n  if (!rule.id || !rule.actions?.length) {\n    throw new Error(`malformed rule (id=${rule.id}): needs id and >=1 action`);\n  }\n}","typeGuard":"function isUpsertableRule(r: unknown): r is PolicyRule {\n  const rule = r as PolicyRule;\n  return typeof rule?.id === 'string' && rule.id.length > 0 &&\n    Array.isArray(rule.actions) && rule.actions.length > 0;\n}","tryCatchPattern":"try {\n  engine.upsertRule(rule);\n} catch (err) {\n  if (err instanceof Error && err.message === 'invalid-policy-rule') {\n    return reportConfigError(`rule ${JSON.stringify(rule)} lacks id/actions`);\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["policy","validation","rules"],"backgroundTag":"schema-validation-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}