{"record":{"id":"bef1608d61f104bb","repo":"affaan-m/ECC","slug":"tool-effect-forbidden","errorCode":"tool.effect_forbidden","errorMessage":"tool ${name} is ${tool.effect_class}, above the allowed ${options.maxEffectClass || 'SE2'}","messagePattern":"tool (.+?) is (.+?), above the allowed (.+?)","errorType":"error_code","errorClass":"ReplayError","httpStatus":null,"severity":"error","filePath":"scripts/lib/eval-harness/replay.js","lineNumber":128,"sourceCode":"  const emit = (entry) => {\n    calls.push(entry);\n    if (typeof options.onCall === 'function') {\n      options.onCall(entry);\n    }\n  };\n\n  return {\n    mode,\n    calls,\n    call(name, args = {}) {\n      const tool = tools[name];\n      if (!tool) {\n        throw new ReplayError('tool.unknown', `tool ${name} is not declared`);\n      }\n      const rank = envelope.effectRank(tool.effect_class);\n      if (rank > maxRank) {\n        emit({ tool: name, mode, status: 'refused', code: 'tool.effect_forbidden' });\n        throw new ReplayError('tool.effect_forbidden', `tool ${name} is ${tool.effect_class}, above the allowed ${options.maxEffectClass || 'SE2'}`, { tool: name });\n      }\n      if (mode === 'replay') {\n        if (rank >= envelope.effectRank('SE3')) {\n          emit({ tool: name, mode, status: 'refused', code: 'tool.effect_forbidden' });\n          throw new ReplayError('tool.effect_forbidden', `tool ${name} (${tool.effect_class}) can never be replayed`, { tool: name });\n        }\n        const record = store.get(name, args);\n        emit({ tool: name, mode, status: 'replayed', fixture_key: record.key, args_hash: record.args_hash, response_hash: record.response_hash });\n        return record.response;\n      }\n      const response = tool.impl(args);\n      const record = store.put(name, args, response);\n      emit({ tool: name, mode, status: 'recorded', fixture_key: record.key, args_hash: record.args_hash, response_hash: record.response_hash });\n      return response;\n    },\n  };\n}\n","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/eval-harness/replay.js#L110-L146","documentation":"replayer.call throws ReplayError('tool.effect_forbidden') when the tool's effect rank exceeds options.maxEffectClass (default 'SE2'), refusing high-effect tools such as SE3+ (network, money-touching, counterparty-facing operations). A second, unconditional variant fires in replay mode for any tool ranked SE3 or above — such tools can never be replayed from fixtures regardless of maxEffectClass. The call is also recorded with status 'refused'.","triggerScenarios":"Calling a tool declared SE3/SE4 with default options (maxEffectClass 'SE2'); in replay mode, calling any tool with rank >= SE3 even if maxEffectClass was raised; passing a maxEffectClass string whose rank is lower than the tool's (e.g. 'SE1').","commonSituations":"Trying to replay a live-payment or network tool in tests — the library intentionally forbids this; forgetting to raise maxEffectClass for a record-mode suite that legitimately exercises SE3 tools; new team members attempting to 'fix' fixture gaps by replaying expensive tools; running the same suite config in replay that was written for record.","solutions":["For record mode with legitimate high-effect tools, raise the ceiling: createReplayer(tools, { mode: 'record', store, maxEffectClass: 'SE3' }).","For replay mode, do not attempt to replay SE3+ tools — split them out of the replayed suite or replace them with a lower-effect, deterministic stub tool (e.g. SE1 fake) declared in the tools map.","If the tool was declared with too high an effect class by mistake, correct its effect_class to the accurate level.","Inspect r.calls for entries with status 'refused' and code 'tool.effect_forbidden' to find all offending calls before re-running."],"exampleFix":"// before: replaying a money tool — forbidden\nconst tools = { chargeCard: { effect_class: 'SE3', determinism: 'nondeterministic', impl } };\nr.call('chargeCard', { amount: 10 }); // throws even with fixture\n\n// after: use a deterministic SE1 stub for replay\nconst tools = {\n  chargeCard: { effect_class: 'SE3', determinism: 'nondeterministic', impl },\n  chargeCardStub: { effect_class: 'SE1', determinism: 'deterministic', impl: () => ({ ok: true }) },\n};\nr.call('chargeCardStub', { amount: 10 });","handlingStrategy":"validation","validationCode":"const { effectRank } = require('./envelope');\nconst maxRank = effectRank(options.maxEffectClass || 'SE2');\nfor (const name of plannedToolCalls) {\n  const t = tools[name];\n  if (effectRank(t.effect_class) > maxRank || (mode === 'replay' && effectRank(t.effect_class) >= effectRank('SE3'))) {\n    throw new Error(`${name} (${t.effect_class}) cannot be called in this suite configuration`);\n  }\n}","typeGuard":"function callableInSuite(tool, maxEffectClass, mode, envelope) {\n  const rank = envelope.effectRank(tool.effect_class);\n  return rank <= envelope.effectRank(maxEffectClass || 'SE2') &&\n    !(mode === 'replay' && rank >= envelope.effectRank('SE3'));\n}","tryCatchPattern":"try {\n  return replayer.call(name, args);\n} catch (e) {\n  if (e instanceof ReplayError && e.code === 'tool.effect_forbidden') {\n    console.warn(`Refused high-effect tool ${name}; use a lower-effect stub or record mode`);\n    return stubResponse(name, args);\n  }\n  throw e;\n}","preventionTips":["Never attempt to replay SE3+ (network/payment) tools — replace them with deterministic low-effect stubs","Raise maxEffectClass explicitly for record-mode suites that legitimately exercise high-effect tools","Check replayer.calls for status:'refused' entries after each run to catch silent refusals","Review effect_class declarations when adding money-touching or network tools — the fence is intentional"],"tags":["effect-fence","security","policy","replay"],"backgroundTag":"permission-denied","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}