{"record":{"id":"8457f5cc03d7e771","repo":"can1357/oh-my-pi","slug":"ambiguous-overlap-for-provider-model-on-ax","errorCode":null,"errorMessage":"ambiguous overlap for `${provider}/${model}` on axis `${axis}`: rules `${first}` and `${second}` tie; add an explicit priority","messagePattern":"ambiguous overlap for `(.+?)/(.+?)` on axis `(.+?)`: rules `(.+?)` and `(.+?)` tie; add an explicit priority","errorType":"exception","errorClass":"AmbiguousOverlapError","httpStatus":null,"severity":"critical","filePath":"packages/catalog/src/compat/cascade.ts","lineNumber":155,"sourceCode":"\nfunction rankCompare(a: readonly [number, number, number], b: readonly [number, number, number]): number {\n\treturn a[0] - b[0] || a[1] - b[1] || a[2] - b[2];\n}\n\nfunction contest(\n\twinners: WinnerTable,\n\taxes: Record<string, unknown> | undefined,\n\trank: readonly [number, number, number],\n\trule: IndexedRule,\n\ttarget: ResolveTarget,\n): void {\n\tif (!axes) return;\n\tfor (const axis in axes) {\n\t\tconst held = winners[axis];\n\t\tif (held) {\n\t\t\tconst order = rankCompare(held.rank, rank);\n\t\t\tif (order === 0) {\n\t\t\t\tthrow new AmbiguousOverlapError(\n\t\t\t\t\ttarget.provider,\n\t\t\t\t\ttarget.model,\n\t\t\t\t\taxis,\n\t\t\t\t\theld.rule.compiled.source,\n\t\t\t\t\trule.compiled.source,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (order > 0) continue;\n\t\t}\n\t\twinners[axis] = { rank, rule };\n\t}\n}\n\nfunction collect(winners: WinnerTable, pick: (rule: CompiledRule) => Record<string, unknown> | undefined) {\n\tconst out: Record<string, unknown> = {};\n\tfor (const axis in winners) {\n\t\tout[axis] = pick(winners[axis].rule.compiled)?.[axis];\n\t}","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/catalog/src/compat/cascade.ts#L137-L173","documentation":"AmbiguousOverlapError thrown by contest() during resolveOverIndex when two compatibility rules with the same rank both match a provider/model target for the same axis. The rule tree cannot decide a winner, so resolution fails fast instead of silently picking one. The message tells you exactly which two rule sources tie.","triggerScenarios":"resolveModelPolicy / cascade resolution where two KDL rules (e.g. a class rule and a providers rule of equal rank) both define the same axis (e.g. thinking efforts) for the same model and neither has an explicit `priority=`.","commonSituations":"Adding a new providers/*.kdl rule that overlaps an existing classes/*.kdl rule for a model; splitting a broad rule into two without priority hints; upstream catalog additions making a wildcard rule collide with an exact one of equal rank.","solutions":["Open the two rule sources named in the message and add `priority=` to the one that should win.","Alternatively narrow one rule's selector (more specific class/provider/family) so ranks no longer tie.","Run `bun run gen:compat` after the KDL edit and verify resolution with a lookup of the affected model id."],"exampleFix":"// before (providers/foo.kdl)\nrule \"gpt-5 thinking\" { providers \"foo\"; efforts ... }\n// after: break the tie explicitly\nrule \"gpt-5 thinking\" { providers \"foo\"; priority 10; efforts ... }","handlingStrategy":"validation","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  const resolved = resolveModelPolicy(provider, model);\n} catch (err) {\n  if (err instanceof AmbiguousOverlapError) {\n    // err names the axis and the two tied rule sources\n    logger.error(\"ambiguous compat rules\", { model, axis: err.axis });\n  }\n  throw err;\n}","preventionTips":["Add explicit `priority=` whenever a new KDL rule could overlap an existing one on the same axis.","Keep classes/*.kdl rules lineage-only and providers/*.kdl rules deployment-only to reduce overlap.","Run the compat test suite after adding rules; equal-rank overlaps throw at resolve time in tests too."],"tags":["catalog","compat","kdl","configuration"],"backgroundTag":"ambiguous-rule-overlap","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}