{"record":{"id":"5fba53b98dbbf0b0","repo":"ruvnet/ruflo","slug":"depth-underflow","errorCode":"depth-underflow","errorMessage":"cannot delegate further — delegationDepth=${currentScope.delegationDepth}","messagePattern":"cannot delegate further — delegationDepth=(.+?)","errorType":"exception","errorClass":"AuthorizationPropagationError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/authorization/propagator.ts","lineNumber":152,"sourceCode":"  constructor(private readonly opts: { provenanceBufferMax?: number } = {}) {}\n\n  /**\n   * Attach a reduced scope to an outbound SendMessage.\n   *\n   * Invariants enforced (throws `AuthorizationPropagationError` on violation):\n   *   - newly granted tools MUST be a subset of `currentScope.grantedTools`\n   *   - newly granted servers MUST be a subset of `currentScope.grantedServers`\n   *   - delegationDepth MUST decrement by ≥ 1 (must remain ≥ 0)\n   *   - principalId is propagated unchanged\n   *   - expiresAt cannot be extended; copied from the holder\n   */\n  wrapOutbound<T>(\n    payload: T,\n    currentScope: AuthScope,\n    requested: { tools?: ReadonlyArray<string>; servers?: ReadonlyArray<string> } = {},\n  ): SendMessageEnvelope<T> {\n    if (currentScope.delegationDepth <= 0) {\n      throw new AuthorizationPropagationError(\n        'depth-underflow',\n        `cannot delegate further — delegationDepth=${currentScope.delegationDepth}`,\n      );\n    }\n    const now = Date.now();\n    if (currentScope.expiresAt <= now) {\n      throw new AuthorizationPropagationError(\n        'scope-expired',\n        `scope expired at ${new Date(currentScope.expiresAt).toISOString()}`,\n      );\n    }\n\n    const reducedTools = subsetOrThrow(\n      currentScope.grantedTools,\n      requested.tools ?? currentScope.grantedTools,\n      'tools',\n    );\n    const reducedServers = subsetOrThrow(","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/authorization/propagator.ts#L134-L170","documentation":"AgentAuthorizationPropagator.wrapOutbound enforces ADR-144's monotonically-reducing scope: each delegation hop consumes at least one unit of delegationDepth. Wrapping an outbound message with a scope whose delegationDepth <= 0 throws AuthorizationPropagationError code 'depth-underflow' — the delegation chain is longer than the original grant permitted, and depth can never be replenished mid-chain.","triggerScenarios":"Agent C calls wrapOutbound with the scope it received when that scope's depth already hit 0; a retry/re-forward path re-wraps an already-wrapped scope, burning depth a second time; scopes minted with delegationDepth: 0 intended as 'leaf agents may not delegate' actually hit the throw.","commonSituations":"Swarm topologies with 3+ delegation hops built from a root scope with a small depth budget; message-forwarding middleware that wraps envelopes again; test fixtures hand-building scopes with default 0 depth instead of makeLegacyPermissiveScope().","solutions":["Have the receiving agent execute the work itself — at depth 0 delegation is forbidden by design","Request a fresh scope with a higher delegationDepth from the original principal (out-of-band)","Audit each hop: call wrapOutbound exactly once per SendMessage; re-wrapping an envelope's scope double-decrements","Mint the root scope with delegationDepth >= the maximum expected chain length"],"exampleFix":"// before\nconst envelope = propagator.wrapOutbound(msg, scope); // scope.delegationDepth === 0 → depth-underflow\n\n// after\nif (scope.delegationDepth <= 0) {\n  await handleLocally(msg); // leaf: execute, don't delegate\n} else {\n  const envelope = propagator.wrapOutbound(msg, scope);\n}","handlingStrategy":"validation","validationCode":"import type { AuthScope } from './authorization/propagator.js';\nfunction canDelegate(scope: AuthScope, now = Date.now()): boolean {\n  return scope.delegationDepth > 0 && scope.expiresAt > now;\n}\nif (!canDelegate(scope)) throw new Error('scope cannot delegate — request a fresh one from the principal');\nconst envelope = propagator.wrapOutbound(msg, scope);","typeGuard":"function isAuthPropagationError(e: unknown, code?: string): boolean {\n  return e instanceof Error && e.name === 'AuthorizationPropagationError'\n    && (code === undefined || (e as { code?: string }).code === code);\n}","tryCatchPattern":"try {\n  return propagator.wrapOutbound(msg, scope);\n} catch (e) {\n  if (isAuthPropagationError(e, 'depth-underflow')) {\n    return handleLocally(msg); // execute at this hop instead of delegating\n  }\n  throw e;\n}","preventionTips":["Mint root scopes with delegationDepth >= maximum expected chain length","Wrap the original payload exactly once per hop — never re-wrap an envelope's scope","Unit-test deep chains against the depth budget before shipping topologies"],"tags":["security","authorization","delegation","agent-communication"],"backgroundTag":"delegation-depth-exceeded","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"}