Skip to content

Commit 571c3d5

Browse files
author
Mateusz
committed
ACP workspace policy, backend/session updates, and OAuth connector spec/docs
Made-with: Cursor
1 parent fe8ccb2 commit 571c3d5

54 files changed

Lines changed: 1413 additions & 616 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.kiro/specs/oauth-connectors-plugin-architecture/design-review.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The technical design addresses decoupling of OAuth-oriented backends from core e
1111

1212
| Issue | Resolution in `design.md` |
1313
|-------|---------------------------|
14-
| CLI hook timing vs parser build | **Lifecycle ordering** documented: `backend_imports.py` / `discover_plugin_backends` completes synchronously before `ArgumentParserBuilder.build()` in `cli.py`. |
14+
| CLI hook timing vs parser build | **Lifecycle ordering** documented: `cli.py` import pulls in `backend_imports`, which runs `discover_backends()` (including `discover_plugin_backends()`) **before** `main()` `parse_cli_args()``ArgumentParserBuilder.build()`; alternate imports called out for tests/tools. |
1515
| Plugin dependency direction for protocols | **`ITokenRefresher` / `ICredentialRotator` / `IOAuthAccountSelector`**: definitions live under `src/core/interfaces/` for core use; **must be re-exported** from `src/core/plugin_api.py` so external packages never deep-import `src.core.interfaces`. |
1616
| Parsed CLI args not applied to config | **`config_applicator_hook`** on `BackendPluginDefinition`; **`ConfigurationApplicator`** iterates hooks as post-applicator phase via domain-specific applicator pipeline. |
1717

@@ -47,11 +47,12 @@ Tracked in `design.md` and `requirements.md` rather than as gate items:
4747
- **REQ 4.3**: Plugin-owned config schema extension is specified at the design level (hook + documentation); full dynamic Pydantic merge may evolve during implementation.
4848
- **`Protocol` runtime checks**: Prefer methods over `@property` on `@runtime_checkable` protocols (see `design.md`). `IOAuthAccountSelector` `@runtime_checkable` is optional.
4949
- **Cross-repo versioning**: `PluginCompatibility` has `core_min_version`/`core_max_version` but runtime version comparison is not yet defined.
50+
- **Pre-import OAuth filter vs YAML**: Normative section in `requirements.md` plus `design.md` pre-import table and optional `BackendPluginDefinition` registration flags document how `connectors/__init__.py` can honor capability semantics before module import.
5051

5152
## Final assessment
5253

53-
**Decision**: **GO** (design phase -- subject to normal `spec.json` task approval before implementation).
54+
**Decision**: **GO** for implementation (`spec.json` marks requirements, design, and tasks **approved**; `ready_for_implementation` is **true**).
5455

55-
**Rationale**: Critical lifecycle, plugin API boundary, ITokenRefresher reconciliation, and configuration application concerns are fully captured in the current design. All P0/P1 audit findings resolved. Remaining items are scope documentation and implementation-detail hardening, not architectural blockers.
56+
**Rationale**: Critical lifecycle, plugin API boundary, ITokenRefresher reconciliation, configuration application, pre-import capability equivalence, and CLI ordering (default vs alternate paths) are captured. Earlier P0/P1 audit findings remain resolved. Residual items are follow-up scope (deferred modules, runtime semver checks) and normal implementation hardening.
5657

57-
**Next steps**: Approve `tasks.md` in `spec.json` when satisfied; run `/kiro:validate-design` or project checks if the workflow requires a fresh validation stamp after spec edits.
58+
**Next steps**: Execute `tasks.md` under the project’s Kiro `/kiro:spec-impl` or standard engineering workflow; run `/kiro:validate-design` or repository checks if the team requires a fresh validation stamp after substantive spec edits.

.kiro/specs/oauth-connectors-plugin-architecture/design.md

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ The core already has substantial OAuth infrastructure:
4545
- `streaming_executor.py` defines `ITokenRefresher` protocol (`refresh_token_if_needed(force_reload, session_id, retry_after_seconds) -> bool`) but retains legacy duck-typing helpers: `_is_oauth_auto_refresher` (checks `"oauth-auto" in backend_type` via `getattr`), `_apply_refreshed_auth_header` (accesses `_oauth_credentials` dict via `getattr`), `_get_oauth_auto_selection_strategy` / `_get_oauth_auto_available_account_count` (access `_account_selector` via `getattr`), and `_record_rate_limit` (duck-typed `getattr(token_refresher, "record_rate_limit", None)`).
4646
- `resilience/scope.py` uses hardcoded `_PERSONAL_BACKEND_TYPES` frozenset with **7 entries** (`antigravity-oauth`, `gemini-cli-cloud-project`, `gemini-oauth-free`, `gemini-oauth-plan`, `openai-codex`, `opencode-zen`, `qwen-oauth`) plus a fallback `"oauth" in normalized or "codex" in normalized` substring heuristic. Also supports runtime config overrides via `resilience.shared_backend_types` / `resilience.personal_backend_types`.
4747
- `argument_parser_builder.py` contains **9 hardcoded** backend-specific debugging override flags in `_add_debugging_override_arguments` (e.g. `--enable-gemini-oauth-auto-backend-debugging-override`, `--enable-kiro-oauth-auto-backend-debugging-override`). Additionally has 3 in-repo OAuth behavior flags: `--disable-gemini-oauth-fallback`, `--disable-gemini-oauth-reasoning-prompt-injection`, `--allow-oauth-auto-replacement` (deferred — see `requirements.md`).
48-
- CLI parsing runs before full plugin discovery in the staged initialization path.
48+
- **CLI vs discovery ordering (default entry point)**: `src/core/cli.py` imports `src.core.services.backend_imports` at module import time; that module calls `discover_backends()` immediately, which runs built-in connector import plus `discover_plugin_backends()` before `main()` calls `parse_cli_args()``ArgumentParserBuilder.build()`. A **second** `discover_backends()` during `ApplicationBuilder` startup is idempotent. Therefore, for `python -m src.core.cli`, **full backend/plugin discovery completes before the CLI parser is built**.
49+
- **Alternate entry points**: Unit tests or tools that construct `ArgumentParserBuilder` (or `ArgumentParser`) **without** first importing `src.core.cli` (or otherwise calling `discover_backends()`) may run with **no** registered plugin CLI hooks. Tests that need hooks must import discovery side effects explicitly or call `discover_backends(force=True)` after test fixtures reset discovery state.
4950
- Test suite has `pytest.importorskip("llm_proxy_oauth_connectors...")` in `test_qwen_oauth_retry.py` and `test_vendor_prefix.py`.
5051

5152
This spec formalizes and cleans up the remaining implicit contracts.
@@ -60,7 +61,7 @@ This spec formalizes and cleans up the remaining implicit contracts.
6061
- Extend `BackendCapabilityDescriptor` (`requires_personal_auth`, `is_oauth_based`) to replace name-based classification.
6162
- Reconcile/extend existing `ITokenRefresher` instead of creating parallel `ICredentialRotator`/`IOAuthAccountSelector` where possible.
6263
- Add `cli_arguments_hook` and `config_applicator_hook` to `BackendPluginDefinition` (natural extension of existing `post_build_hook`).
63-
- **CLI lifecycle note (critical)**: Plugin discovery must complete before `ArgumentParserBuilder.build()` or a two-phase parsing strategy is required.
64+
- **CLI lifecycle note (critical)**: For the supported CLI entry point, plugin discovery already runs at `cli` import time (see above). Implementation work must **preserve** that ordering when adding CLI hooks and must document the **non-default** import path for tests. If a future entry point ever parses before discovery, adopt two-phase parsing or move discovery earlier for that entry point only.
6465

6566
### Technology Stack
6667

@@ -74,7 +75,7 @@ This spec formalizes and cleans up the remaining implicit contracts.
7475
| Requirement | Summary | Components | Interfaces |
7576
|-------------|---------|------------|------------|
7677
| 1.1–1.3 | Core independence (in-scope) | `oauth_detector.py`, `scope.py` | `BackendCapabilityDescriptor` |
77-
| 2.1–2.3 | Capability declaration | `BackendCapabilityDescriptor` | `BackendCapabilityDescriptor` |
78+
| 2.1–2.4 | Capability declaration | `BackendCapabilityDescriptor`, `BackendPluginDefinition` | `BackendCapabilityDescriptor`, plugin registration metadata |
7879
| 3.1–3.5 | Execution decoupling | `streaming_executor.py` | `ITokenRefresher` (relocated + extended), `ICredentialRotator`, `IOAuthAccountSelector` |
7980
| 4.1–4.5 | Configuration and CLI | `argument_parser_builder.py`, `plugin_api.py`, `ConfigurationApplicator` | `BackendPluginDefinition`, optional schema hook |
8081
| 5.1–5.4 | Test isolation + packaging exception | Core tests, plugin repo | N/A |
@@ -117,6 +118,18 @@ Add `requires_personal_auth: bool = False` and `is_oauth_based: bool = False` to
117118
- **Integration**: Update `oauth_detector.py` and `scope.py` to consult these flags (plus any existing neutral signals such as `has_static_credentials` on the class when supplied) instead of static connector lists for in-scope classification.
118119
- **YAML config update**: All in-repo backends that are OAuth-based or require personal auth must have their `capability_descriptor` sections updated in config YAML templates (e.g., `gemini-oauth-plan`, `gemini-oauth-free`, `openai-codex`, etc.). This ensures the new flags are populated even without plugin hooks.
119120

121+
##### Pre-import connector walk (`src/connectors/__init__.py`)
122+
123+
The auto-discovery loop may call `is_oauth_connector(module_name)` **before** importing a connector module. YAML-backed `BackendCapabilityDescriptor` instances are **not** available at that instant. Align implementation with [Capability signals and discovery timing](requirements.md#capability-signals-and-discovery-timing-normative) in `requirements.md`:
124+
125+
| Approach | When to use | Notes |
126+
|----------|-------------|-------|
127+
| **Plugin definition flags** | Entry-point backends | Add `is_oauth_based` / `requires_personal_auth` (names illustrative) to `BackendPluginDefinition` so `oauth_detector` / discovery can read OAuth semantics from registration metadata without hardcoding extracted logical names. |
128+
| **Core manifest or generated map** | In-repo `pkgutil` modules | Small map keyed by module slug, maintained next to YAML or generated in CI from the same `capability_descriptor` source, read without importing connector implementation. |
129+
| **Reorder / import-then-filter** | Only if explicitly accepted | Document threat model if OAuth connector module import must run before skip logic. |
130+
131+
`scope.py` operates on configured backends and **can** rely on merged `BackendCapabilityDescriptor` from YAML after config load; the pre-import constraint applies primarily to `oauth_detector` and the `connectors` package walk.
132+
120133
### Core Plugin API
121134

122135
#### BackendPluginDefinition
@@ -139,6 +152,11 @@ Add `requires_personal_auth: bool = False` and `is_oauth_based: bool = False` to
139152
@dataclass(frozen=True)
140153
class BackendPluginDefinition:
141154
# ... existing fields ...
155+
# Registration-time OAuth/capability hints (illustrative names — align with
156+
# BackendCapabilityDescriptor in implementation). Required so pre-import
157+
# discovery never depends on extracted connector name literals.
158+
is_oauth_based: bool = False
159+
requires_personal_auth: bool = False
142160
cli_arguments_hook: Callable[[argparse.ArgumentParser], None] | None = None
143161
config_applicator_hook: Callable[[argparse.Namespace, AppConfig], AppConfig] | None = None
144162
# Optional: register_backend_config_models or equivalent — see REQ 4.3 note above.
@@ -176,7 +194,8 @@ class BackendPluginDefinition:
176194

177195
- **`ITokenRefresher`** (existing, relocated): Covers routine and forced token refresh. Already used as the typed parameter in `StreamingExecutor` methods. Remains the primary interface for token refresh.
178196
- **`ICredentialRotator`** (new, extends `ITokenRefresher`): Adds rate-limit-aware credential rotation, credential snapshot access (replacing `_oauth_credentials` duck-typing), and rate-limit recording (replacing `_record_rate_limit` duck-typing). Implementors MUST also satisfy `ITokenRefresher`.
179-
- **`IOAuthAccountSelector`** (new, standalone): Covers account selection strategy and available account count. Accessed separately from `ITokenRefresher` via `isinstance` checks on the same object or on a dedicated selector attribute exposed through a public method.
197+
- **`IOAuthAccountSelector`** (new, standalone): Covers account selection strategy and available account count.
198+
- **Canonical wiring rule (this spec)**: Multi-account OAuth token refreshers implement **both** `ICredentialRotator` and `IOAuthAccountSelector` on the **same object** passed into `streaming_executor`. Core uses `isinstance(token_refresher, ICredentialRotator)` and `isinstance(token_refresher, IOAuthAccountSelector)` on that object only—**no** `getattr` on `_account_selector`, no nested private selector objects. If a future connector needs a separate selector instance, core may introduce an explicit public accessor method on `ICredentialRotator` in a follow-up spec; it is **out of scope** for the initial migration.
180199

181200
##### Service interface (illustrative)
182201

@@ -228,10 +247,10 @@ class ICredentialRotator(ITokenRefresher, Protocol):
228247
*,
229248
retry_after_seconds: float | None = None,
230249
) -> None:
231-
"""Record a rate-limit event for accounting/monitoring.
232-
250+
"""Record a rate-limit event for accounting/monitoring (synchronous).
251+
233252
Replaces duck-typed getattr(token_refresher, 'record_rate_limit', None).
234-
May be sync or async; callers should handle both."""
253+
Must be non-blocking and safe to call from async contexts without await."""
235254
...
236255

237256
@runtime_checkable
@@ -258,7 +277,7 @@ class IOAuthAccountSelector(Protocol):
258277

259278
- Prefer **methods** (`get_*`) over `@property` on `@runtime_checkable` protocols: CPython's runtime structural checks for `isinstance` are unreliable for protocol properties in many versions.
260279
- `IOAuthAccountSelector` does **not** require `isinstance` runtime dispatch in the current architecture (it is always accessed via a known code path from `ICredentialRotator` implementors). If this remains the case during implementation, the `@runtime_checkable` decorator is optional and can be omitted to sidestep CPython quirks. Keep it if future polymorphic dispatch is anticipated.
261-
- If properties are required for backward compatibility during migration, avoid `isinstance` on that protocol; use `getattr` with **public** method names only as a temporary bridge, or use explicit adapter objects owned by core.
280+
- If properties are required for backward compatibility during migration, avoid `isinstance` on that protocol; use `getattr` with **public** method names only as a temporary bridge, or use explicit adapter objects owned by core. **Do not** bridge via private attributes such as `_account_selector` (requirement 3.4).
262281

263282
##### Migration path for current duck-typed access
264283

@@ -297,7 +316,7 @@ class IOAuthAccountSelector(Protocol):
297316
**Implementation Notes**
298317

299318
- **Integration**: Remove flags such as `--enable-gemini-oauth-auto-backend-debugging-override`. Add `_add_plugin_arguments` that invokes registered hooks.
300-
- **Lifecycle ordering (critical)**: Current implementation runs `parse_cli_args()` / `ArgumentParserBuilder.build()` **before** full plugin discovery in staged initialization. This must be resolved (either move discovery earlier or adopt two-phase parsing). Documented as a key risk in `research.md`.
319+
- **Lifecycle ordering**: Default `cli.py` path already runs `discover_backends()` at import time before `parse_cli_args()`; keep that invariant when wiring hooks. See [Existing Architecture Analysis](#existing-architecture-analysis) and `research.md` for alternate import paths.
301320
- **Config application**: `ConfigurationApplicator` (or a small helper invoked from it) iterates `config_applicator_hook`s with `Namespace` + `AppConfig` so plugins can inject into `BackendConfig.extra` or other extension fields.
302321

303322
## Testing Strategy

.kiro/specs/oauth-connectors-plugin-architecture/gap-analysis.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
## 2. Requirements Feasibility Analysis
1818

19-
- **Core Independence from Plugin Names**: Feasible. Requires replacing hardcoded lists in `oauth_detector.py` and `scope.py` with **capability flags** on `BackendCapabilityDescriptor` (not a separate global registry). Note: `oauth_detector.py` already has partial dynamic capability via entry-point discovery; the refactor formalizes and completes this.
20-
- **Capability Declaration**: Feasible. The `BackendCapabilityDescriptor` needs to be extended with `requires_personal_auth` and `is_oauth_based` flags. Pydantic v2 `model_validate` handles new fields with defaults automatically.
19+
- **Core Independence from Plugin Names**: Feasible. Requires replacing hardcoded lists in `oauth_detector.py` and `scope.py` with **declared capability metadata** (`BackendCapabilityDescriptor` where YAML is loaded; registration-time plugin fields and/or a core manifest for pre-import `connectors/__init__.py` paths — see `requirements.md`). Note: `oauth_detector.py` already has partial dynamic capability via entry-point discovery; the refactor formalizes and completes this.
20+
- **Capability Declaration**: Feasible. Extend `BackendCapabilityDescriptor` with `requires_personal_auth` and `is_oauth_based`. Mirror OAuth semantics on `BackendPluginDefinition` (or equivalent) for entry points evaluated before connector import. Pydantic v2 `model_validate` handles new descriptor fields with defaults automatically.
2121
- **Execution Decoupling**: Feasible. `streaming_executor.py` already defines `ITokenRefresher` protocol. New `ICredentialRotator` (extending `ITokenRefresher`) and `IOAuthAccountSelector` protocols must be defined in `src/core/interfaces/`. `ICredentialRotator` also adds `get_current_access_token()` and `record_rate_limit()` to replace remaining duck-typed access patterns. `ITokenRefresher` must be relocated from `streaming_executor.py` to `src/core/interfaces/`.
2222
- **Configuration and CLI Independence**: Feasible but requires architectural changes. The `ConfigurationApplicator` uses a domain-specific applicator delegation pattern (15+ applicators); plugin hooks should be invoked as a post-applicator phase. The 9 debug override flags and 3 deferred CLI flags need careful classification.
2323
- **Test Isolation**: Highly feasible. Requires moving OAuth-specific tests (`test_qwen_oauth_retry.py`, relevant cases in `test_vendor_prefix.py`) to the `llm-interactive-proxy-oauth-connectors` repository and using mock plugins for testing discovery in the core repo.
@@ -47,12 +47,12 @@
4747

4848
## 4. Implementation Complexity & Risk
4949

50-
- **Effort**: **L (1-2 weeks)**. Requires significant refactoring of core execution paths (`streaming_executor.py`), CLI building, and moving a large number of tests across repository boundaries.
50+
- **Effort**: **L (on the order of one to two weeks)**. Touches core execution (`streaming_executor.py`), discovery/CLI wiring, and a **focused** set of cross-repo test moves (see `tasks.md` Phase 4 — not a bulk test migration).
5151
- **Risk**: **Medium**. The changes touch critical paths (streaming execution, CLI startup), but the goal is to formalize existing implicit contracts into explicit interfaces, which is a known and safe pattern.
5252

5353
## 5. Recommendations for Design Phase
5454

5555
- **Preferred Approach**: Option A (Extend Existing Plugin Discovery). Focus on relocating/extending existing `ITokenRefresher`, defining `ICredentialRotator` (extending `ITokenRefresher`) and `IOAuthAccountSelector`, and extending `BackendCapabilityDescriptor`.
5656
- **Research Needed**:
57-
- How to safely allow plugins to register CLI arguments without causing conflicts or breaking the `argparse` lifecycle.
58-
- The exact shape of the generic interfaces needed by `streaming_executor.py` to replace `_oauth_credentials`, `_account_selector`, and `record_rate_limit` access. (Largely resolved in `design.md` migration path table.)
57+
- Namespace / collision rules for plugin CLI flags (documentation + optional guardrails).
58+
- Runtime semver comparison for `PluginCompatibility` if plugins start enforcing `core_max_version` strictly. (Interface shapes for `streaming_executor.py` are resolved in `design.md` migration table.)

0 commit comments

Comments
 (0)