feat(acms): add context strategy registry with plugin discovery #565
No reviewers
Labels
No labels
auto/needs-reevaluation
controller-managed
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
Bounty
$100
Bounty
$1000
Bounty
$10000
Bounty
$20
Bounty
$2000
Bounty
$250
Bounty
$50
Bounty
$500
Bounty
$5000
Bounty
$750
MoSCoW
Could have
MoSCoW
Must have
MoSCoW
Should have
Needs Feedback
Points
1
Points
13
Points
2
Points
21
Points
3
Points
34
Points
5
Points
55
Points
8
Points
88
Priority
Backlog
Priority
CI Blocker
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
State
Completed
State
Duplicate
State
In Progress
State
In Review
State
Paused
State
Unverified
State
Verified
State
Wont Do
Type
Automation
Type
Bug
Type
Discussion
Type
Documentation
Type
Epic
Type
Feature
Type
Legendary
Type
Refactor
Type
Support
Type
Task
Type
Testing
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Blocks
#191 feat(acms): add context strategy registry
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core!565
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/m6-acms-strategy-registry"
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
Implements the ACMS context strategy registry with plugin discovery, enabling extensible context assembly strategies within the ACMS pipeline. Per spec §25162–25233, §28682–28708, §42628–42653, and §43167–43199.
Changes
Domain Models (
src/cleveragents/domain/models/acms/strategy.py)ContextStrategyprotocol defining the strategy interface (name,capabilities,can_handle,execute,explain)StrategyCapabilitiesmodel with supported resource types and backend requirementsBackendSet,PlanContext,StrategyConfigconfiguration modelsContextStrategyResultwith deterministic fragment ordering (-relevance_score,uko_node)MappingProxyTypeforStrategyConfig.extraandContextStrategyResult.stats(ADR-004 immutability)Built-in Stub Strategies (
src/cleveragents/domain/models/acms/strategy_stubs.py)simple-keyword,semantic-embedding,breadth-depth-navigator,arce,temporal-archaeology,plan-decision-context@functools.cached_propertyfor capabilities (avoids per-access Pydantic allocation)Strategy Registry (
src/cleveragents/application/services/strategy_registry.py)StrategyRegistrywithregister(),register_from_module()(plugin discovery),enable()/disable()threading.RLockfor thread safety on all public methodsallowed_module_prefixesallowlist for dynamic import security (CWE-706)Documentation (
docs/reference/context_strategies.md)Tests
features/context_strategy_registry.feature)robot/context_strategy_registry.robot)benchmarks/context_strategy_bench.py)Closes #191
feat(acms): add context strategy registry with plugin discovery (#191)to feat(acms): add context strategy registry with plugin discovery@hamza.khyari — This PR has no milestone assigned, no labels, and a merge conflict with master. I've added
State/In Review,Priority/Low,Type/Featurelabels and assigned it to milestone v3.5.0.Action required: Please rebase this branch (
feature/m6-acms-strategy-registry) onto the latest master to resolve the merge conflict. The ACMS v1 pipeline (#188) has since merged, which likely caused the conflict. If this PR has been superseded by other work, please close it with an explanation.This is blocking the strategy registry work item (#191) which is on the M5 critical path.
2ebce4da561942b9ef6a1942b9ef6a98c1fa3125Code Review — Commit
98c1fa31(feat(acms): add context strategy registry)Reviewer scope: Bug detection, security, performance, thread safety, test quality, and spec conformance against issue #191 and
docs/specification.md.Overall the implementation is well-structured: the domain models, protocol design, and 55 BDD scenarios are thorough. However, I found several issues worth addressing before merge.
1. BUGS
1.1 [HIGH]
register()can raiseAttributeErrorinstead ofStrategyRegistrationErrorFile:
src/cleveragents/application/services/strategy_registry.py:113When
name=None(the default), the code accessesstrategy.namebefore the protocol check at line 121:If a caller passes a non-protocol object without an explicit
name, this raises a rawAttributeErrorrather than the intendedStrategyRegistrationError. The existing test sidesteps this by always passingname="fake". Fix: move theisinstancecheck before the name resolution, or wrapstrategy.nameaccess in a try/except.1.2 [MEDIUM]
temporal-archaeologycan_handleonly checks graph, not cold-tierFile:
src/cleveragents/domain/models/acms/strategy_stubs.py:291-298Spec §25215 / §43193-43195 says this strategy requires Graph + Cold tier. Capabilities correctly declare
uses_graph=True, uses_temporal=True, butcan_handleonly checksbackends.graph is not None. There is no cold-tier check becauseBackendSethas no temporal/cold field (see finding 3.1). The strategy reports confidence 0.5 when only a graph backend exists, even if no cold-tier data is available.1.3 [MEDIUM]
plan-decision-contextcan_handleunconditionally returns 0.7File:
src/cleveragents/domain/models/acms/strategy_stubs.py:347-353Spec §43197-43199 says this strategy "operates on warm/cold tiers", yet
can_handlenever inspects backend availability at all. Every other strategy properly degrades to 0.0 when its required backends are missing; this one does not. The comment says "reads from warm/cold tiers" but there is no tier-availability check.2. SPEC DEVIATIONS
2.1 [MEDIUM] Default enabled list — spec internal conflict
Spec §25223 lists 4 strategies in the default enabled config:
Spec §28682 (config key table) lists 3:
The implementation follows §28682 (3 strategies,
arceexcluded). The spec has an internal inconsistency — this should be clarified with the spec author and the decision documented.2.2 [MEDIUM] Custom strategy module path format diverges from spec
Spec §25226 shows custom strategy registration using dot notation:
The implementation's
register_from_modulerequires colon notation:These formats are incompatible. If a user follows the spec's TOML example,
register_from_modulewill reject it. Note the spec's own pipeline component format at §25232 uses colons ("my_extensions.scorers:DomainAwareScorer"), so the spec itself is inconsistent — but the strategy registration example uses dots.3. DESIGN GAPS
3.1 [MEDIUM]
BackendSetlacks temporal/cold-tier fieldFile:
src/cleveragents/domain/models/acms/strategy.py:35-58BackendSetmodelstext,vector, andgraphbut has no field for temporal/cold-tier backends. Two of six strategies (temporal-archaeology,plan-decision-context) declareuses_temporal=Truein capabilities, but there is no way to verify temporal backend availability. This is the root cause of bugs 1.2 and 1.3.3.2 [LOW]
update_config()doesn't exposeresource_typesorextraFile:
src/cleveragents/application/services/strategy_registry.py:324-374StrategyConfighasresource_typesandextrafields, butupdate_config()only acceptsenabled,timeout_seconds,max_fragments,max_workers, andcircuit_breaker_threshold. There is no way to update those fields through the public API.4. SECURITY
4.1 [MEDIUM]
register_from_module— arbitrary code execution via dynamic import (CWE-706)File:
src/cleveragents/application/services/strategy_registry.py:186Semgrep flagged
importlib.import_module(module_name)as CWE-706. Module-level code runs on import,__init__runs on instantiation — both are arbitrary code execution vectors. The docstring has a security note, which is good, but there is no enforcement: no allowlist of permitted module prefixes, no namespace restriction. Consider adding a configurable allowlist or restricting to known package namespaces.4.2 [LOW] Mutable dicts on frozen Pydantic models violate ADR-004
Files:
strategy.py:244(StrategyConfig.extra),strategy.py:293(ContextStrategyResult.stats)Both are
dict[str, Any]onfrozen=Truemodels. Pydantic'sfrozenprevents attribute reassignment but not dict mutation (config.extra["key"] = "value"succeeds). If ADR-004 immutability is a hard requirement, these should usetypes.MappingProxyTypeor a frozen representation.5. PERFORMANCE
5.1 [MEDIUM]
capabilitiesproperty creates a new Pydantic model on every accessFile:
src/cleveragents/domain/models/acms/strategy_stubs.py(all 6 classes)Every stub constructs a fresh
StrategyCapabilitiesPydantic model on each.capabilitiesaccess:This property is also called inside
can_handle()(self.capabilities.quality_score), so everycan_handleinvocation triggers Pydantic model construction + validation. Spec target is < 20ms for all strategycan_handlechecks combined (§43253). With 6 strategies, that is 12+ Pydantic allocations per polling cycle.Fix: Use
functools.cached_propertyor store as a class-level constant.6. THREAD SAFETY
6.1 [MEDIUM]
StrategyRegistryhas no synchronization for concurrent accessFile:
src/cleveragents/application/services/strategy_registry.py:77-81The registry uses plain
dictandlistfor internal state with no locking. Other services in the same package usethreading.Lock(semantic_validation_service.py:210) orthreading.RLock(autonomy_guardrail_service.py:59). The spec describes strategies being executed in parallel (§42636). If the registry is a shared singleton, concurrent reads during parallel strategy execution could race with configuration updates.7. TEST QUALITY
7.1 [LOW] Tests access private
_enabled_orderattributeFile:
features/steps/context_strategy_registry_steps.py(step_given_stale_enabled)This couples the test to the internal data structure. If the enabled-order tracking changes, this test breaks silently. A public test-helper method or factory would be more robust.
7.2 [LOW] No test for
register(non_protocol_obj)without explicit nameBug 1.1 has no test coverage.
step_when_register_non_protocolalways passesname="fake", so thestrategy.nameaccess on line 113 is never exercised with a non-protocol object that lacks.name.7.3 [LOW] No concurrent access tests
Given finding 6.1, there are no tests exercising the registry under concurrent reads/writes.
Summary Table
register()raisesAttributeErrorinstead ofStrategyRegistrationErrortemporal-archaeologydoesn't verify cold-tier availabilityplan-decision-contextnever degrades — returns 0.7 unconditionallyarce)BackendSetmissing temporal/cold fieldupdate_config()doesn't exposeresource_types/extraregister_from_moduledynamic import with no allowlist (CWE-706)capabilitiescreates new Pydantic model on every access_enabled_orderThe highest-priority items are bug 1.1 (wrong exception type — easy fix), perf 5.1 (easy fix with
cached_property), and the spec deviations 2.1/2.2 (need spec-author clarification).@ -0,0 +212,4 @@def assemble(self,request: ContextRequest,[TEST - LOW] Directly accessing the private
_enabled_orderattribute couples this test to the internal implementation. If the enabled-order tracking mechanism changes, this test breaks silently. Consider adding a public test-helper method onStrategyRegistry(e.g.,_inject_stale_entry_for_test()) or a factory that produces a registry in this state.@ -0,0 +76,4 @@def __init__(self) -> None:"""Initialize an empty registry."""self._strategies: dict[str, ContextStrategy] = {}[THREAD SAFETY - MEDIUM] These three plain
dict/listfields have no synchronization. Other services in the same package (e.g.,autonomy_guardrail_service.py,semantic_validation_service.py) usethreading.Lockorthreading.RLock. Since the spec describes parallel strategy execution (§42636), this registry will likely be a shared singleton accessed from multiple threads. Consider adding athreading.RLockto protect mutation methods (register,unregister,set_enabled,update_config,clear).@ -0,0 +110,4 @@already registered, or the strategy doesn't satisfythe ``ContextStrategy`` protocol."""name = name or strategy.name[BUG - HIGH] When
name=None(default) and the passed object is not aContextStrategy, this line accessesstrategy.namebefore the protocol check at line 121. If the object lacks a.nameattribute, this raisesAttributeErrorinstead ofStrategyRegistrationError.Fix: Move the
isinstance(strategy, ContextStrategy)check before this line, or wrap thestrategy.nameaccess in a try/except that converts toStrategyRegistrationError.@ -0,0 +183,4 @@module_name, class_name = module_path.rsplit(":", 1)try:module = importlib.import_module(module_name)[SECURITY - MEDIUM] CWE-706:
importlib.import_module()with a runtime-determined string executes arbitrary module-level code on import. The docstring warning is good, but there is no enforcement — no allowlist of permitted module prefixes or namespace restriction.Consider adding a configurable allowlist (e.g., only
cleveragents.*modules or explicitly configured trusted namespaces) to prevent loading untrusted code from user-supplied TOML configs.@ -0,0 +32,4 @@# ---------------------------------------------------------------------------class BackendSet(BaseModel, frozen=True):[DESIGN - MEDIUM]
BackendSetonly modelstext,vector, andgraph. There is no field for temporal/cold-tier backends. Two of six strategies (temporal-archaeology,plan-decision-context) declareuses_temporal=Truein capabilities but have no way to verify temporal backend availability via this model. This is the root cause of thecan_handlebugs in those two strategies. Consider adding atemporalorcoldbackend field.@ -0,0 +241,4 @@default=(),description="Resource types this strategy is limited to (empty = all)",)extra: dict[str, Any] = Field([SECURITY - LOW / ADR-004]
extra: dict[str, Any]on afrozen=Truemodel can still be mutated via dict operations (config.extra["key"] = "value"succeeds). Pydantic'sfrozenonly prevents attribute reassignment, not deep mutation. Same issue exists forContextStrategyResult.statsat line 293. If ADR-004 immutability is a hard requirement, considertypes.MappingProxyTypeor a frozen representation.@ -0,0 +52,4 @@return "simple-keyword"@propertydef capabilities(self) -> StrategyCapabilities:[PERF - MEDIUM] This constructs a new
StrategyCapabilitiesPydantic model on every property access. Sincecan_handle()readsself.capabilities.quality_score, eachcan_handlecall allocates and validates a Pydantic instance. With 6 strategies and a spec target of < 20ms total for allcan_handlechecks (§43253), this is wasteful.Fix: Use
functools.cached_propertyto compute once and cache:This applies to all 6 stub strategy classes.
@ -0,0 +293,4 @@request: ContextRequest,backends: BackendSet,) -> float:if backends.graph is not None:[BUG - MEDIUM] Spec §25215 / §43193-43195 says
temporal-archaeologyrequires Graph + Cold tier. Capabilities correctly declareuses_temporal=True, butcan_handleonly checksbackends.graph. The root cause is thatBackendSethas no temporal/cold field — but this means the strategy reports 0.5 confidence even when cold-tier data is unavailable, which could lead to it being selected and then failing at assembly time.@ -0,0 +350,4 @@backends: BackendSet,) -> float:# Works even without graph; reads from warm/cold tiers.return self.capabilities.quality_score[BUG - MEDIUM] This unconditionally returns 0.7 regardless of backend/tier availability. Spec §43197-43199 says this strategy "operates on warm/cold tiers", yet it never checks whether those tiers are available. Every other strategy degrades to 0.0 when its required backends are missing; this one does not. This could cause the strategy to be selected when it has no data source to read from.
98c1fa312509e3c18aba09e3c18abacf622c8cd6Review Response — All 14 Findings Addressed
Thanks for the thorough review, @CoreRasurae. All findings have been addressed in commit
cf622c8c. Here's the breakdown:1. BUGS
1.1 [HIGH]
register()guard ordering — Fixed. Moved theisinstance(strategy, ContextStrategy)check beforestrategy.nameaccess. Usestype(strategy).__name__as fallback label in the error message when the object has no.nameattribute.1.2 [MEDIUM]
temporal-archaeologycan_handlemissing cold-tier check — Fixed.can_handlenow checksbackends.graph is not None and backends.temporal is not None, returning 0.0 if either is absent. See also 3.1 below for theBackendSet.temporalfield.1.3 [MEDIUM]
plan-decision-contextunconditional 0.7 — Fixed.can_handlenow checksbackends.temporal is not None, returning 0.0 otherwise. Added regression tests for both the degraded (0.0) and happy (0.7) paths.2. SPEC DEVIATIONS
2.1 [MEDIUM] Default enabled list conflict (§25223 vs §28682) — Documented. Added a comment in
strategy_stubs.pyexplaining the contradiction and the rationale for following §28682 (3 strategies). ARCE requires all 3 backends, so excluding it from defaults is defensible for minimal deployments.2.2 [MEDIUM] Colon vs dot notation — Documented. Added a
Note:section in theregister_from_moduledocstring explaining that §25226 (dot) and §25232 (colon) are inconsistent within the spec, and we chose colon notation because it unambiguously separates module from class name, matching Python entry-point conventions.3. DESIGN GAPS
3.1 [MEDIUM]
BackendSetmissing temporal field — Fixed. Addedtemporal: object | None = Field(default=None)toBackendSet. This unblocks thecan_handlechecks in 1.2 and 1.3. Typed asobject | Noneas a placeholder until aTemporalBackendprotocol is defined.3.2 [LOW]
update_config()missingresource_types/extra— Fixed. Added both parameters toupdate_config(). New BDD scenarios verify thatresource_typesis persisted correctly and thatextrais wrapped inMappingProxyTypefor immutability.4. SECURITY
4.1 [MEDIUM] No import allowlist (CWE-706) — Fixed. Added
allowed_module_prefixesconstructor parameter (default("cleveragents.",)) with a check beforeimportlib.import_module(). Module paths not matching any allowed prefix raiseStrategyRegistrationErrorwith a clear message.4.2 [LOW] Mutable dicts on frozen models — Fixed.
StrategyConfig.extraandContextStrategyResult.statschanged fromdict[str, Any]toMappingProxyType[str, Any], withfield_validator(mode="before")to coerce incoming plain dicts.model_config = ConfigDict(arbitrary_types_allowed=True)added to both models.5. PERFORMANCE
5.1 [MEDIUM]
capabilitiesproperty allocates on every access — Fixed. All 6 stubs changed from@propertyto@functools.cached_property. The Pydantic model is constructed once per instance and cached in__dict__thereafter.6. THREAD SAFETY
6.1 [MEDIUM] No synchronization — Fixed. Added
threading.RLocktoStrategyRegistry.__init__. All mutation methods (register,set_enabled,update_config,unregister,clear) and all query methods (get,get_entry,list_all,list_enabled,list_builtin,validate_registry,is_registered,__contains__,__len__) are wrapped withwith self._lock:. RLock chosen over Lock to support nested calls (e.g.,update_config→get_entry).7. TEST QUALITY
7.1 [LOW] Private
_enabled_orderaccess — Fixed. Addedinject_stale_enabled_entry()as a public test helper onStrategyRegistry. The step now uses the public helper instead of reaching into internals. Added tovulture_whitelist.py.7.2 [LOW] Missing test for bug 1.1 — Fixed. New scenario: "Reject non-protocol object without explicit name" exercises
register(object())without a name parameter, assertingStrategyRegistrationError.7.3 [LOW] No concurrent tests — Fixed. New scenario: "Concurrent register and list_enabled do not raise" uses 10 threads with a
Barrierfor synchronized start. Post-condition assertions verify the registry contains all expected strategies (6 builtins + 4 thread-registered) and that every thread-registered name is present.Additional Changes (self-review)
During self-review, found and fixed 3 additional issues:
is_registered(),__contains__(), and__len__()were readingself._strategieswithout the lock — now all three acquireself._lockMappingProxyTypefield validator tests (plain dict coercion, non-dict rejection)update_configtests for the newresource_typesandextraparametersTest count: 72 scenarios, 0 failed. Lint, typecheck, and vulture all pass.
Review: feat(acms): add context strategy registry with plugin discovery
Reviewed per
docs/development/review_playbook.md. Routing: domain models & services → primary Luis, secondary Jeff.Luis's prior
REQUEST_CHANGESreview appears fully addressed — all 14 findings have been resolved. Nice work on the CWE-706 allowlist for dynamic imports and the clean separation betweenContextStrategyprotocol andStrategyRegistryEntrymetadata.P1:must-fix —
set_enabled()does not reject duplicate namesset_enabled()acceptsnames: list[str]and assignsself._enabled_order = list(names)directly. If the caller passes["a", "b", "a"], theenabled_setdedup for config updates works correctly, but_enabled_orderpreserves duplicates. This violates Invariant 2 documented in the class body (set(_enabled_order) is a subset of _strategies.keys()— the invariant implies it should behave like a set). Downstream code inlist_enabled()would return["a", "b", "a"], causing duplicate strategy execution.Fix: Either deduplicate while preserving order (
list(dict.fromkeys(names))) or raise on duplicates. Dedup-with-warning is probably safest for config-driven callers.File:
src/cleveragents/application/services/strategy_registry.py,set_enabled()method, near theself._enabled_order = list(names)assignment.P2:should-fix — PR body claims "40 BDD scenarios" but feature file has 55+
The PR description says "40 BDD scenarios" but
features/context_strategy_registry.featurecontains 55+ scenarios. The count is stale from an earlier draft. Not a code issue, but misleading for reviewers.P2:should-fix — Missing
__all__exports instrategy.pyandstrategy_stubs.pyThe domain model modules
strategy.pyandstrategy_stubs.pydon't declare__all__. Whilestrategy_registry.pyre-exports the key types, explicit__all__in each module is the project convention (per CONTRIBUTING.md § public API).Process notes
Priority/Highbut PR hasPriority/Low. Should match.mergeable: false). Rebase needed.REQUEST_CHANGESshould be dismissed since all items are addressed.Overall this is solid work — the registry design is clean, thread safety is well-handled, and the BDD coverage is thorough. The P1 duplicate-name issue is the only blocker.
@ -0,0 +237,4 @@):raise StrategyRegistrationError(f"Module '{module_name}' is not in the allowed prefix list: "f"{self._allowed_module_prefixes}. Only modules under these "P1:must-fix —
set_enabled()does not validate for duplicate names in the input list.enabled_set = set(names)correctly deduplicates for the config-update loop, butself._enabled_order = list(names)preserves any duplicates from the caller. This violates Invariant 2 (set(_enabled_order)should behave like a proper set) and would causelist_enabled()to return duplicate entries, leading to duplicate strategy execution.Fix: Either
self._enabled_order = list(dict.fromkeys(names))to deduplicate while preserving order, or raiseValueErroron duplicate input. Deduplicate-with-warning is probably safest for config-driven callers.cf622c8cd6b7effcafc1Response to @brent.edwards Review
Thanks for the review, Brent. All items addressed:
P1:
set_enabled()duplicate names — FixedChanged
self._enabled_order = list(names)toself._enabled_order = list(dict.fromkeys(names)), which deduplicates while preserving insertion order. Added BDD scenario "Set enabled list deduplicates duplicate names" that passes["simple-keyword", "arce", "simple-keyword"]and asserts the result is["simple-keyword", "arce"].P2: Stale scenario count in PR body — Fixed
Updated PR description from "40 BDD scenarios" to "73 BDD scenarios" and added mentions of thread safety tests, MappingProxyType validators, and other additions since the original draft.
P2: Missing
__all__— FixedAdded
__all__to bothstrategy.py(7 public symbols) andstrategy_stubs.py(8 public symbols, isort-sorted per RUF022).Process notes
fae438a7). Conflict inacms/__init__.pywas from the analyzer plugin PR — resolved by keeping both sides. PR is nowmergeable: true.Test count: 73 scenarios, 0 failed. Lint, typecheck clean.
Verification of @hamza.khyari Response (Round 2)
Checked out the branch locally and ran the full quality gate suite. Results below.
P1:
set_enabled()deduplication — VerifiedConfirmed
strategy_registry.pynow useslist(dict.fromkeys(names)). BDD scenario "Set enabled list deduplicates duplicate names" passes with the expected["simple-keyword", "arce"]output.P2:
__all__exports — Verifiedstrategy.py: 7 symbols in__all__strategy_stubs.py: 8 symbols in__all__ruff check --select RUF022passes clean on both files.P2: Scenario count — Minor discrepancy
PR body and response comment both state 73 BDD scenarios. Actual test run reports 72 scenarios passed, 0 failed (64 plain Scenarios + 2 Scenario Outlines expanding to 8 rows = 72). Off by one — likely a counting error in the PR description. Not blocking.
Quality gates
nox -s lintAll checks passed!nox -s typecheck0 errors, 0 warnings, 0 informationsnox -s unit_tests -- features/context_strategy_registry.featureVerdict
All code fixes from my original review are confirmed. The only remaining item is the 72 vs 73 scenario count in the PR body — please update the description to say "72" when convenient.
No blocking issues from my side. The stale
REQUEST_CHANGESfrom @CoreRasurae is the only remaining gate for merge.Approved by Claude Opus.