fix(resource-registry): use full inheritance chain in contextual binding type compatibility #3336
Labels
Clear labels
auto/needs-reevaluation
controller-managed
overdue
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Needs Feedback
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
Controller deferred this PR; awaiting Phase 6+ scope-evaluator or operator re-enablement.
Auto-agents controller manages this PR/issue (see tools/controller/deploy/RUNBOOK.md). Remove this label to abandon controller management.
PR blocked by an open issue dependency. Operator must close the dep (or remove the dependency link) before the merge driver can act. Auto-cleared by merge_drive when no open deps remain.
Most recent merge cycle hit CI timeout. Driver excludes this PR while last merge_cycle row is < 30 min old; label persists thereafter as visible history.
Currently being processed by an implementer worker.
Currently being processed by the merge driver.
Currently being processed by a reviewer worker.
Merge driver heartbeat stale; pipeline halted. Closed automatically on next clean tick.
Detected master commit violating the strict merge invariant. Tracked as an issue (not a PR label); kept here for label completeness.
In-cycle escalation: most recent attempt ran at the Tier 0 slot (`tier-0`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 1 slot (`tier-1`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 2 slot (`tier-2`). Slot's model defined in .opencode/models/tiers.yaml. Gated behind IMPLEMENTER_ESCALATION_TIER2_ENABLED.
In-cycle escalation: most recent attempt ran at the Tier -1 slot (`tier-min`). Slot's model defined in .opencode/models/tiers.yaml. Suffix is ``-min`` (not ``--1``) so the Forgejo UI reads naturally.
Tracking issues used by the AI Automation system for agents to communicate and report.
Rebase conflict needs LLM conflict-resolver.
Failing CI needs implementer attention.
Documenting a driver incident or rollback.
Reviewer has APPROVED this PR and no later REQUEST_CHANGES is outstanding. The merge driver requires this label to even consider a PR for merging. Set by the reviewer worker on APPROVE; cleared on REQUEST_CHANGES.
Train repeatedly lost master-tempo races. Driver excludes via merge_cycle until cooldown elapses; label persists as visible history.
Revert PR backing out an invariant violation. Fast-tracked through the merge driver.
Sentinel PR duplicated from upstream into a personal fork by tools/duplicate_prs_to_fork.py for pipeline testing. Lives only in the fork; the canonical pipeline never sees it.
No implementer activity for N days. Flagged for human review. Auto-cleared on next push to head branch.
Repeatedly fails on current master (>= 3 ci-fail-on-rebased-sha releases in 12 h). Excluded from driver until human triage.
A ticket in a blocked state and unable to complete until some other task is completed first.
Bounty
$100
A bounty of $100 for any open-source contributor who provides a MR that solves this issue
Bounty
$1000
A bounty of $1000 for any open-source contributor who provides a MR that solves this issue
Bounty
$10000
A bounty of $10000 for any open-source contributor who provides a MR that solves this issue
Bounty
$20
A bounty of $20 for any open-source contributor who provides a MR that solves this issue
Bounty
$2000
A bounty of $2000 for any open-source contributor who provides a MR that solves this issue
Bounty
$250
A bounty of $250 for any open-source contributor who provides a MR that solves this issue
Bounty
$50
A bounty of $50 for any open-source contributor who provides a MR that solves this issue
Bounty
$500
A bounty of $500 for any open-source contributor who provides a MR that solves this issue
Bounty
$5000
A bounty of $5000 for any open-source contributor who provides a MR that solves this issue
Bounty
$750
A bounty of $750 for any open-source contributor who provides a MR that solves this issue
MoSCoW
Could have
Could have feature in order to satisfy the epic/legendary.
MoSCoW
Must have
Must have feature in order to satisfy the epic/legendary.
MoSCoW
Should have
Should have feature in order to satisfy the epic/legendary.
There are questions in the ticket that can not be completed until the project owner provides clarity.
Points
1
1 man-hours worth of work for an expert with no learning curve.
Points
13
13 man-hours worth of work for an expert with no learning curve.
Points
2
2 man-hours worth of work for an expert with no learning curve.
Points
21
21 man-hours worth of work for an expert with no learning curve.
Points
3
3 man-hours worth of work for an expert with no learning curve.
Points
34
34 man-hours worth of work for an expert with no learning curve.
Points
5
5 man-hours worth of work for an expert with no learning curve.
Points
55
55 man-hours worth of work for an expert with no learning curve.
Points
8
8 man-hours worth of work for an expert with no learning curve.
Points
88
88 man-hours worth of work for an expert with no learning curve.
Priority
Backlog
This ticket has backlogged priority and is not to be worked on yet
Priority
CI Blocker
Critical priority issue that blocks CI/CD pipeline and prevents PR merges
Priority
Critical
The priority is critical
Priority
High
The priority is high
Priority
Low
The priority is low
Priority
Medium
The priority is medium
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
A ticket for learning a tool or technology that is needed to be able to do future planning and design.
State
Completed
The ticket has been fully implemented, completed, and merged with the source code. This label should only be applied once a ticket is closed.
State
Duplicate
A ticket that represents the same content as an existing ticket.
State
In Progress
A ticket that is actively being developed.
State
In Review
A ticket that has had some code completed to implement but is waiting to pass peer review and is not yet merged in.
State
Paused
This ticket's work started but wasn't finished. It's on hold (likely in a feature branch) and will be resumed later, either due to a blocker or a delay.
State
Unverified
All new tickets start in this state. A developer may set it to show the ticket is unverified. This means we haven't agreed to work on it. It will either move to a verified state or be closed as wontdo.
State
Verified
The issue has been verified by a developer as legitimate. It will be worked on and verified tickets are now considered part of the backlog.
State
Wont Do
This ticket has been decided it wont be done. This may mean the bug has been determined to not be real (cant verify) or the feature is one we have decided we dont want to adopt.
Type
Automation
Any edits or discussion about the AI automated coding system.
Type
Bug
Something that doesnt work as intended.
Type
Discussion
Anytime a ticket represents a discussion about a subject and doesnt fall into one of the other categories.
Type
Documentation
An error or improvement needed in the documentation.
Type
Epic
Any first tier epic. That is, an epic which contains only issues as children and will not have sub-epics.
Type
Feature
Some new functionality not present.
Type
Legendary
A type of Epic which will contain other Epics.
Type
Refactor
A code change that restructures existing code without changing its external behavior.
Type
Support
Someone needs help using the project.
Type
Task
A generic task that doesnt fit into the other type categories.
Type
Testing
Work exclusively focusing on fixing or expanding testing.
No Label
Projects
Clear projects
No project
Assignees
aditya (Aditya Chhabra)
aleenaumair (Aleena Umair)
brent.edwards (Brent Edwards)
CoreRasurae (Luis Mendes)
drew (Drew Morris)
eugen.thaci (Eugen Thaci)
freemo (Jeffrey Phillips Freeman)
HAL9000 (HAL 9000)
HAL9001 (HAL9001)
hamza.khyari (Hamza Khyari)
hurui200320 (Rui Hu)
justin.morris
khird (Kyle Hird)
org.cleveragents
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Blocks
#2929 UAT: bug(resource-registry): contextual resource binding type compatibility check does not use full inheritance chain
cleveragents/cleveragents-core
Reference: cleveragents/cleveragents-core#3336
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "bugfix/binding-resolution-inheritance-chain"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes a bug in
binding_resolution_service.pywhere_is_type_compatibleperformed only a flat one-level parent check againstspec.parent_types, causing contextual resource binding to incorrectly reject valid bindings when the actual resource type was related to the required type through a multi-level inheritance chain (e.g., grandchild → child → parent).Changes
src/cleveragents/application/services/binding_resolution_service.py:required_type in spec.parent_typescheck in_is_type_compatiblewith a delegation toself._registry.is_subtype_of(actual_type, required_type), which internally callsresolve_inheritance_chain()fromcleveragents.resource.inheritanceto walk the full ADR-042 inheritance chain to any depth.ResourceTypeSpecimport that was only needed to accessparent_typesdirectly._check_type_compatibilityis implicitly fixed as it delegates to_is_type_compatible— no changes required there.features/consolidated_binding_resolution.feature:features/steps/binding_resolution_steps.py:is_subtype_of(), backed by the realis_subtype_of()function fromcleveragents.resource.inheritanceoperating over an_inheritsdict — ensuring tests exercise the actual inheritance logic rather than a stub.the resource type X inherits from nothingandthe resource type X inherits from Yto allow feature files to declaratively construct inheritance hierarchies.Design Decisions
registry.is_subtype_of()rather than re-implementing chain traversal:ResourceRegistryServicealready exposesis_subtype_of()as a public method. Using it keeps the API boundary clean and avoids duplicating or leaking knowledge of_load_type_registry()internals into the binding resolution layer.is_subtype_of()function fromcleveragents.resource.inheritance(not a hand-rolled stub), so the new scenarios genuinely validate the inheritance traversal behaviour end-to-end at the unit/BDD level.resolve_inheritance_chain()oris_subtype_of(): The root cause was entirely in the call site — the underlying chain-resolution logic incleveragents.resource.inheritancewas already correct per ADR-042. The fix is therefore minimal and surgical.Testing
Related Issues
Closes #2929
Automated by CleverAgents Bot
Supervisor: Implementation | Agent: ca-issue-worker
🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3336-1775373400]
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
🔍 Code Review — REQUEST CHANGES
Reviewed PR #3336 with focus on api-consistency, naming-conventions, and code-patterns.
This is a well-scoped, surgical bugfix that correctly addresses the ADR-042 inheritance chain traversal issue. The core change — delegating
_is_type_compatibletoregistry.is_subtype_of()instead of performing a flatparent_typesmembership check — is the right approach. The test coverage is thorough with 4 new BDD scenarios covering 2-level chains, 3-level chains, unrelated-type rejection, and static binding via inheritance.However, there is one required change related to API contract consistency, and two PR metadata issues.
Required Changes
[API-CONSISTENCY] Registry protocol contract is stale in docstrings
src/cleveragents/application/services/binding_resolution_service.py__init__docstring (around line 62) states:is_subtype_of(type_name, ancestor_name)on the registry. Furthermore,show_type()is no longer called anywhere in this service (the only call site was in the old_is_type_compatible), so it is no longer a required method from this service's perspective.is_subtype_of(type_name, ancestor_name)as a required method. Consider whethershow_typeshould be removed from the documented contract (it may still be needed by other callers of the same registry, but this service no longer uses it).[METADATA] PR is missing milestone
[METADATA] PR is missing
Type/labelType/label. This is a bugfix, so it should haveType/Bug.Type/Buglabel.Deep Dive: API Consistency
Given special attention to API consistency across the change:
is_subtype_of(type_name, ancestor_name)onResourceRegistryServicematches the parameter order used in the call site (self._registry.is_subtype_of(actual_type, required_type)) —actual_typemaps totype_nameandrequired_typemaps toancestor_name. This is correct.is_subtype_of_mockcorrectly delegates to the realis_subtype_of()fromcleveragents.resource.inheritance, ensuring tests exercise actual inheritance logic rather than a hand-rolled stub. Excellent practice.actual_type == required_typefast-path andNotFoundError → Falsefallback are both preserved by the underlyingis_subtype_of()implementation ininheritance.py(equality check at line 238, exception catch at lines 243-248).Deep Dive: Naming Conventions
_is_type_compatibleand_check_type_compatibilityfollow the project's underscore-prefixed private method conventionstep_type_inherits_nothing,step_type_inherits_from) follow thestep_prefix conventionis_subtype_of_mockname clearly indicates it's a mock implementationDeep Dive: Code Patterns
is_subtype_of()rather than reimplementing chain traversal — respects Service Layer / Repository boundary_is_type_compatibleis now a thin wrapper, keeping binding resolution focused on binding logicResourceTypeSpecimport was properly removedOther Checks
ISSUES CLOSED: #2929footerCloses #2929: Present in PR body# type: ignore: None foundDecision: REQUEST CHANGES 🔄
The code logic is correct and well-tested. The only blocking issue is the stale API contract documentation in the service docstrings, plus the missing PR metadata (milestone and label).
Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-pr-self-reviewer
🔍 Code Review — COMMENT (Test-Focused)
Reviewed PR #3336 with focus on test-coverage-quality, test-scenario-completeness, and test-maintainability.
This is a well-scoped bugfix with a clean implementation — delegating
_is_type_compatibletoregistry.is_subtype_of()is the right approach. The previous reviewer already flagged the stale docstrings and missing PR metadata (milestone +Type/label), which I concur with. My review focuses specifically on the test dimension.Deep Dive: Test Coverage Quality
✅ Real inheritance logic in mocks: The
is_subtype_of_mockinbinding_resolution_steps.pydelegates to the realis_subtype_of()fromcleveragents.resource.inheritance, building atype_registrydict from the mock's_inheritsmap. This is excellent practice — the tests exercise the actual chain-walking algorithm rather than a hand-rolled stub, giving genuine confidence in the fix.✅ Meaningful scenario assertions: Each new scenario verifies concrete outcomes (resource_id, binding mode, deferred status, or ValidationError text), not just "it didn't crash". The three-level chain scenario (
local/special-dev-container → devcontainer-instance → container-instance) directly reproduces the bug described in issue #2929.✅ Negative testing included: The "rejects unrelated type even with inheritance present" scenario ensures the fix didn't accidentally make type checking too permissive —
fs-directorycorrectly fails to matchcontainer-instanceeven when inheritance infrastructure is active.✅ Existing scenarios still exercise the new code path: The pre-existing "Type compatibility passes for sub-type via parent_types" scenario (which uses
fs-directorywithparent_typesincludinggit-checkout) continues to work becausestep_type_parentwas updated to also populate_inherits, ensuringis_subtype_of()can walk the chain.Deep Dive: Test Scenario Completeness
Issue #2929 acceptance criteria check:
_is_type_compatibleusesis_subtype_of()— verified by all new scenarioscontainer-instancecan bind todevcontainer-instance— two-level scenariogit-checkoutcannot bind to unrelatedfs-directory— unrelated-type scenariofeatures/consolidated_binding_resolution.feature@type_compat @inheritance @static_binding), but no corresponding scenario for parameter binding via inheritance._resolve_parametercalls_check_type_compatibility→_is_type_compatible, which is the fixed method. However, having an explicit BDD scenario would:(a) Document this as a tested requirement
(b) Guard against future regressions if parameter binding is refactored
(c) Satisfy the acceptance criteria checklist completely
Deep Dive: Test Maintainability
[TEST-MAINTAINABILITY] Stale scenario title and comments for NotFoundError case
features/consolidated_binding_resolution.feature, scenario "Type compatibility returns False when show_type raises NotFoundError"# _is_type_compatible -- show_type raises NotFoundError (lines 369-370)) referenceshow_type, but the service no longer callsshow_typein_is_type_compatible. The method now delegates entirely tois_subtype_of(). The scenario still passes becauseis_subtype_of()internally handles unknown types, but the title is now misleading.show_typeis being tested, when in reality it'sis_subtype_of's internal error handling that's being exercised. This creates confusion during maintenance.is_subtype_ofinstead ofshow_type.[TEST-MAINTAINABILITY] Mock registry complexity growth
features/steps/binding_resolution_steps.py,step_mock_registry_resources,_types,_inherits). Thestep_type_parentstep must update both_typesand_inheritsto stay consistent. This dual-update requirement is a potential source of subtle test bugs if a future contributor updates one but not the other.step_type_parentand the newstep_type_inherits_fromproperly maintain both structures), but consider adding a brief comment instep_mock_registryexplaining the relationship between_types(used byshow_type) and_inherits(used byis_subtype_of).[TEST-QUALITY] Single-inheritance assumption in
_inheritsmapfeatures/steps/binding_resolution_steps.py,step_type_parentandstep_type_inherits_from_inheritsdict maps each type to a single parent (str | None), whileparent_typessupports multiple parents. Thestep_type_parentstep usesparents[0]as the direct ancestor. This is fine for current scenarios but would silently produce incorrect results if a future test needs multi-parent inheritance. Worth a brief comment noting this limitation.Other Checks (Standard Criteria)
ISSUES CLOSED: #2929footerCloses #2929: Present in PR body# type: ignore: None foundbinding_resolution_service.py~330 lines,binding_resolution_steps.py~430 lines — both well under 500from cleveragents.resource.inheritance import is_subtype_ofinsidestep_mock_registryis acceptable as it's a test-time lazy import within a step function)@type_compat @inheritanceplus specific tags__init__docstrings still referenceshow_resourceandshow_typebut notis_subtype_ofType/label on PR: Already flagged — should haveType/BugSummary
Decision: COMMENT 💬
The code logic is correct and the test quality is high. The missing parameter binding inheritance scenario (item 1) is the most actionable finding from a test-completeness perspective. The stale scenario title (item 2) is a maintainability concern worth addressing. Items 3 and 4 are non-blocking observations.
Automated by CleverAgents Bot
Reviewer: Code Quality | Agent: ca-pr-self-reviewer
04c18b86b8to3224f2136eAddressed all review feedback from both reviews. Here's a summary of what was implemented:
Changes Made
Required (REQUEST CHANGES review)
[API-CONSISTENCY] Updated registry protocol contract in docstrings (
binding_resolution_service.py):show_resourceandshow_typewithshow_resourceandis_subtype_ofas the required protocol methods__init__docstring: replacedshow_type(name)withis_subtype_of(type_name, ancestor_name)in the Args descriptionshow_typeis no longer documented as a required method since this service no longer calls it[METADATA] Milestone v3.2.0 assigned ✅
[METADATA]
Type/Buglabel added ✅Addressed (COMMENT review)
[TEST-GAP] Added parameter binding inheritance scenario (
features/consolidated_binding_resolution.feature):@type_compat @inheritance @parameter_binding: "Parameter binding resolves via two-level inheritance chain"local/my-devcontainer(typedevcontainer-instance) with a parameter slot requiringcontainer-instance_resolve_parameter→_check_type_compatibility→_is_type_compatible→is_subtype_of()path[TEST-MAINTAINABILITY] Updated stale scenario title and comment (
features/consolidated_binding_resolution.feature):# _is_type_compatible -- show_type raises NotFoundError (lines 369-370)→# _is_type_compatible -- is_subtype_of returns False for unknown typeType compatibility returns False when show_type raises NotFoundError→Type compatibility returns False for unknown typeItems 3 and 4 from the COMMENT review (mock registry complexity and single-inheritance assumption) were non-blocking observations and are acknowledged but not changed, as the current implementation correctly handles all tested scenarios.
Automated by CleverAgents Bot
Supervisor: Implementation | Agent: ca-issue-worker
🔍 Code Review — APPROVED (Performance-Focused)
Reviewed PR #3336 with focus on performance-implications, resource-usage, and scalability.
This is a well-scoped, correct bugfix that addresses a real ADR-042 compliance gap. The previous review cycle's feedback (stale docstrings, missing milestone/label, missing parameter binding scenario, stale scenario titles) has been fully addressed. The implementation correctly delegates type compatibility checking to
registry.is_subtype_of()rather than reimplementing chain traversal.Deep Dive: Performance Implications
Given special attention to the performance characteristics of the change:
Old code path (
_is_type_compatiblebefore this fix):New code path (
_is_type_compatibleafter this fix):Which in
ResourceRegistryService.is_subtype_of()does:Analysis:
show_type→ singlefilter_byquery)_load_type_registry→query(ResourceTypeModel).all())_find_matching_resources(),_is_type_compatibleis called once per linked resource. For a project with N linked resources and a tool with M slots, this means M×N full-table scans instead of M×N single-row queries.Verdict: Non-blocking. This is acceptable for the following reasons:
Correctness over performance: The old code was wrong — it only checked immediate parents, violating ADR-042's polymorphism guarantee. A correct-but-slower implementation is strictly better than a fast-but-broken one.
Pre-existing API design:
ResourceRegistryService.is_subtype_of()and_load_type_registry()already existed with this exact behavior. This PR correctly uses the existing public API rather than reimplementing chain traversal or reaching into private internals. The performance characteristic is a property of the registry service, not of this change.Bounded chain depth:
resolve_inheritance_chain()is bounded byMAX_CHAIN_DEPTH = 5and uses cycle detection, so the chain walk itself is O(1) in practice.Typical scale: Projects typically have a small number of linked resources (single digits), and tools typically have 1-3 resource slots. The M×N product is small in practice.
Future optimization path: If this becomes a bottleneck, the proper fix is adding caching to
_load_type_registry()(e.g., a short-lived TTL cache or per-request memoization), which is orthogonal to this bugfix and should be tracked as a separate optimization issue.⚠️ Recommendation: Consider filing a follow-up issue to add caching to
ResourceRegistryService._load_type_registry(). This would benefit not just binding resolution but all callers ofis_subtype_of(),resolve_type_inheritance_chain(), andremove_type().Deep Dive: Resource Usage
_load_type_registry()method properly uses try/finally to close sessionsis_subtype_ofcall opens and closes a session within the same call frame — no long-held connections{name: {inherits: ..., built_in: ...}}per type) and is garbage-collected after each callDeep Dive: Scalability
resolve_inheritance_chain()uses avisitedset to detect and reject circular inheritanceis_subtype_of()catchesResourceTypeParentNotFoundError,ResourceTypeCircularInheritanceError, andResourceTypeInheritanceDepthError, returningFalsefor any broken chain — this is safe and graceful_find_matching_resources()iterates all linked resources linearly. This is fine for typical project sizes but would not scale to projects with hundreds of linked resources. (This is pre-existing behavior, not introduced by this PR.)Standard Criteria Checks
fix(resource-registry): ...withISSUES CLOSED: #2929Closes #2929, milestone v3.2.0,Type/Buglabel__init__docstrings now listshow_resourceandis_subtype_of# type: ignorebinding_resolution_service.py~330 lines,binding_resolution_steps.py~430 linesfrom cleveragents.resource.inheritance import is_subtype_ofinsidestep_mock_registryis acceptable as a test-time lazy importis_subtype_of_mockdelegates to realis_subtype_of()fromcleveragents.resource.inheritance_is_type_compatibleis now a thin delegationCode Correctness
self._registry.is_subtype_of(actual_type, required_type)correctly maps tois_subtype_of(type_name, ancestor_name)— actual_type is the concrete type being checked, required_type is the ancestoris_subtype_of()ininheritance.pystarts withif type_name == ancestor_name: return Trueis_subtype_of()catches all chain errors and returnsFalse, matching the old behavior whereNotFoundErrorfromshow_type()returnedFalsestep_type_parentwas updated to also populate_inheritsDecision: APPROVED ✅
The fix is correct, minimal, well-tested, and properly addresses the ADR-042 compliance gap. The performance observation about
_load_type_registry()being called per type check is non-blocking and should be addressed as a separate optimization if it becomes a bottleneck.Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-pr-self-reviewer