{"record":{"id":"816ed6e7708b858b","repo":"ruvnet/ruflo","slug":"cannot-compose-capabilities-with-different-scopes","errorCode":null,"errorMessage":"Cannot compose capabilities with different scopes: \"${cap1.scope}\" vs \"${cap2.scope}\"","messagePattern":"Cannot compose capabilities with different scopes: \"(.+?)\" vs \"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/guidance/src/capabilities.ts","lineNumber":451,"sourceCode":"    }\n\n    return chain;\n  }\n\n  /**\n   * Compose two capabilities via intersection.\n   *\n   * - Actions = intersection of both action sets\n   * - Constraints = union of both constraint sets\n   * - Expiry = the tighter (earlier) of the two\n   * - Delegatable = true only if both are delegatable\n   * - Scope and resource must match; throws if they differ\n   *\n   * @throws Error if scope or resource do not match\n   */\n  compose(cap1: Capability, cap2: Capability): Capability {\n    if (cap1.scope !== cap2.scope) {\n      throw new Error(\n        `Cannot compose capabilities with different scopes: \"${cap1.scope}\" vs \"${cap2.scope}\"`\n      );\n    }\n    if (cap1.resource !== cap2.resource) {\n      throw new Error(\n        `Cannot compose capabilities with different resources: \"${cap1.resource}\" vs \"${cap2.resource}\"`\n      );\n    }\n\n    // Actions: intersection\n    const actionSet1 = new Set(cap1.actions);\n    const intersectedActions = cap2.actions.filter(a => actionSet1.has(a));\n\n    // Constraints: union\n    const combinedConstraints = [...cap1.constraints, ...cap2.constraints];\n\n    // Expiry: tightest\n    let expiresAt: number | null = null;","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/guidance/src/capabilities.ts#L433-L469","documentation":"CapabilityAuthority.compose() intersects two capabilities (actions intersected, constraints unioned, tighter expiry), which is only meaningful when both describe the same target. It throws when `cap1.scope !== cap2.scope` because intersecting capabilities over different scopes (e.g. 'global' vs 'workspace') has no well-defined result. Scope must match exactly before the resource check even runs.","triggerScenarios":"compose(capA with scope 'global', capB with scope 'workspace'); composing capabilities minted for different environments or tenants; generic helper code that pairs any two capabilities from a pool without filtering by scope.","commonSituations":"Capabilities issued per-namespace/per-tenant being mixed in a shared pool; refactors that change the scope naming scheme ('ws' vs 'workspace') so older stored capabilities no longer match; multi-environment configs mixing staging and production caps.","solutions":["Only compose capabilities minted with the identical scope string","Re-grant one of the two under the other's scope, then compose","Filter capability pools by `cap.scope === targetScope` before composing","Centralize scope names as constants so naming cannot drift"],"exampleFix":"// before\nconst merged = authority.compose(globalCap, workspaceCap); // scopes differ → throws\n\n// after\nconst pairs = caps.filter(c => c.scope === 'workspace');\nconst merged = pairs.length === 2 ? authority.compose(pairs[0], pairs[1]) : mintFresh();","handlingStrategy":"validation","validationCode":"if (cap1.scope !== cap2.scope) {\n  // different scopes — skip compose or re-grant one side\n}","typeGuard":"const sameScope = (a: Capability, b: Capability): boolean => a.scope === b.scope;","tryCatchPattern":null,"preventionTips":["Filter capability pools by scope before composing","Define scope names as shared constants","Never compose capabilities minted for different tenants/environments"],"tags":["capabilities","authorization","scope","guidance","compose"],"backgroundTag":"error-scope-mismatch","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}