{"record":{"id":"e8781869bb51d795","repo":"ruvnet/ruflo","slug":"cannot-compose-capabilities-with-different-resourc","errorCode":null,"errorMessage":"Cannot compose capabilities with different resources: \"${cap1.resource}\" vs \"${cap2.resource}\"","messagePattern":"Cannot compose capabilities with different resources: \"(.+?)\" vs \"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/guidance/src/capabilities.ts","lineNumber":456,"sourceCode":"  /**\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;\n    if (cap1.expiresAt !== null && cap2.expiresAt !== null) {\n      expiresAt = Math.min(cap1.expiresAt, cap2.expiresAt);\n    } else if (cap1.expiresAt !== null) {\n      expiresAt = cap1.expiresAt;\n    } else if (cap2.expiresAt !== null) {","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/guidance/src/capabilities.ts#L438-L474","documentation":"The second precondition of compose(): after scopes match, `cap1.resource` must equal `cap2.resource`. The resource identifies the target object (a file, memory namespace, API route), and intersecting actions across two different resources is meaningless, so it throws with both resource names in the message. Actions are intersected only after this check passes.","triggerScenarios":"compose(cap for resource 'repo://a', cap for resource 'repo://b'); capabilities granted on different file paths or namespace URIs being merged; resource identifiers that differ only by trailing slash or encoding.","commonSituations":"Path-based resources where one grant used an absolute and one a relative path; URL-encoded vs raw resource strings; renaming a resource (moved file, renamed workspace) so old grants reference stale identifiers.","solutions":["Compose only capabilities whose `resource` strings are byte-identical","Normalize resource identifiers (canonical paths, consistent encoding) at grant time","Re-grant one capability against the other's resource, then compose","Filter pools by resource equality before calling compose()"],"exampleFix":"// before\nconst merged = authority.compose(capRepoA, capRepoB); // resources differ → throws\n\n// after\nif (capRepoA.resource !== capRepoB.resource) {\n  // re-grant capRepoB on capRepoA.resource, or mint a fresh capability\n}\nconst merged = authority.compose(capRepoA, capOnSameResource);","handlingStrategy":"validation","validationCode":"if (cap1.resource !== cap2.resource) {\n  // different targets — skip compose or re-grant on a common resource\n}","typeGuard":"const sameResource = (a: Capability, b: Capability): boolean =>\n  a.resource === b.resource;","tryCatchPattern":null,"preventionTips":["Canonicalize resource identifiers (paths, URIs) at grant time","Re-grant capabilities after a resource is renamed or moved","Compare exact resource strings before calling compose()"],"tags":["capabilities","authorization","resource","guidance","compose"],"backgroundTag":"resource-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"}