12 KiB
status, system, requirements
| status | system | requirements | |
|---|---|---|---|
| current | plugins |
|
Plugin Repository Task Creation System Design
Purpose and boundaries
The plugin system owns the server-side contract that turns an untrusted plugin repository selection into an authoritative repository descriptor. The task service consumes that contract before it writes a new task.
The task system continues to own task validation, idempotency, repository attachment, branch policies, and task persistence. The workspace system continues to own repository rows. The plugin keeps ownership of provider API calls and external credentials.
Requirement mapping
| Requirement | Design section |
|---|---|
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.1 |
Create sequence |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.2 |
Inspection action |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.3 |
Trust boundary |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.4 |
Descriptor validation |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.5 |
Failure contract |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.6 |
Multi-repository preflight |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.7 |
Compatibility |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.8 |
Failure contract |
AC-PLUGINS-REPOSITORY-TASK-CREATION-001.9 |
Responsive behavior |
Components and responsibilities
internal/plugins.Serviceresolves the active manifest owner and invokes its standardized workspace-scopedrepositories.inspectaction. It limits, parses, and validates the response.- A narrow task-service
RepositorySelectionResolverinterface accepts the verified workspace ID and an untrusted repository selection. Production composition adapts the plugin service to this interface. Focused task tests use a stub. internal/task/service.Servicepreflights all first-use plugin selections, replaces each one with an internal trusted descriptor, and only then enters the existing create and repository-attachment sequence.- Task HTTP and WebSocket handlers map typed inspection failures to bounded client errors. The existing task-dialog submit path keeps the dialog open and shows the returned message in its error toast.
- The plugin fixture supplies deterministic list, inspect, and branch action results for backend and Playwright tests. It contains no real credentials.
Data and contracts
No database migration or public task-create schema change is required. The
existing task-create repository fields remain selection hints. The internal
TrustedProviderDescriptor marker remains unavailable to JSON callers.
The task service classifies each selection before persistence:
- An existing
repository_idor an internal trusted descriptor uses its existing server-owned path. - A remote URL with a non-built-in provider uses the plugin resolver.
- A remote URL with a blank or built-in provider is parsed through the existing built-in path. An unsupported, malformed, or provider-mismatched URL fails closed before any task or repository write.
The resolver returns the existing internal repository input shape with a
complete descriptor and TrustedProviderDescriptor set by server code. It
does not return credentials.
Inspection action
A plugin that supports first-use repository selection declares this action:
actions:
- key: "repositories.inspect"
scope: "workspace"
max_body_bytes: 16384
Kandev selects the active plugin from manifest ownership of the requested provider ID. It does not accept a plugin ID from the request. It invokes the action with verified workspace context and a small body:
{
"url": "https://code.example.com/owner/repository"
}
The URL is untrusted. The plugin must first prove that the URL belongs to its configured provider connection. It must not send connection credentials to an origin selected only by this value.
A successful response contains either a repository object or the same object
as the top-level response for compatibility with existing provider actions:
{
"repository": {
"provider_id": "example-provider",
"provider_host": "https://code.example.com",
"provider_scope": "https://code.example.com/context-a",
"provider_repository_id": "immutable-repository-id",
"owner_or_project": "owner",
"name": "repository",
"clone_url": "https://code.example.com/owner/repository.git",
"default_branch": "main"
}
}
{"matched":false} is a normal no-match response. Pull-request inspection
envelopes may contain additional change-request fields, but task repository
resolution reads only the nested repository descriptor.
The host applies the declared request limit, a 15-second deadline, a 1 MiB response limit, and typed action error classification. It does not retry the action inside one task-create attempt.
Trust boundary
The request's provider ID is used only to select a manifest ownership slot. The active registry determines the plugin record. The remote URL and any other request fields are lookup or pinning hints. They never become persisted authority and never enter the Git credential broker.
The plugin response is authoritative because it arrives server-to-server from the active manifest owner under verified workspace context. Installed plugin code is trusted to use its own connection safely, but Kandev still validates the returned descriptor before persistence.
The browser cannot set TrustedProviderDescriptor. The existing plugin Host
Tasks.Create adapter can still set it after its separate authenticated Host
authorization. These are the only two server-owned paths to the trusted
resolver.
Descriptor validation
The plugin service and task service jointly enforce these rules before writes:
- The response provider ID equals the manifest-owned provider ID.
- Provider host is a normalized HTTPS origin without user information.
- Provider scope is either empty or a valid normalized scope.
- Immutable provider repository ID, owner or project, name, clone URL, and default branch are present.
- Clone URL is credential-free HTTPS and its origin matches the provider host.
- Optional immutable request hints, such as provider scope or repository ID, match the response. Mutable owner, name, host, clone URL, and branch values from the browser are ignored.
- The response is within size limits and contains one repository descriptor.
The existing trusted remote repository validator remains the final persistence guard. Validation logic should be shared instead of creating a weaker plugin action path.
Create sequence
sequenceDiagram
actor User
participant UI as Native task dialog
participant Task as Task service
participant Plugins as Plugin service
participant Plugin as Connected code-host plugin
participant DB as Kandev database
User->>UI: Select plugin repository and create task
UI->>Task: Task fields plus repository selection
Task->>Task: Authorize workspace and check external ID
Task->>Plugins: Resolve selection in verified workspace
Plugins->>Plugins: Find active manifest owner
Plugins->>Plugin: repositories.inspect with untrusted URL
Plugin-->>Plugins: Authoritative credential-free descriptor
Plugins->>Plugins: Validate identity, origin, scope, and limits
Plugins-->>Task: Internal trusted descriptor
Task->>Task: Preflight all selected repositories
Task->>DB: Create task, canonical repository, and association
Task-->>UI: Created task
UI-->>User: Open created task
Task-create idempotency stays first. If an external ID already names an existing task, Kandev returns that task without contacting a plugin. For a new task, plugin preflight runs after workspace authorization and request normalization but before the task row insert.
After preflight, the existing resolveTrustedRemoteRepository and
FindOrCreateRepository path preserves canonical provider identity and
deduplicates a repository that another request registered concurrently.
Multi-repository preflight
Kandev resolves repository selections in request order into an in-memory list. It validates every plugin descriptor before it creates a task or repository row. A failure discards the list and returns an error.
The service can resolve selections sequentially in the first implementation. This avoids concurrent calls to the same plugin connection and keeps error ordering deterministic. Repository database insertion continues through the existing canonical identity lock after all external work succeeds.
This boundary prevents the issue's empty task and partially registered plugin repository set. It does not make later blocker, branch-policy, attachment, or database failures transactional. That broader create-atomicity work remains separate.
Failure contract
Inspection failures use typed categories instead of string matching:
| Failure | REST result | WebSocket result | User result |
|---|---|---|---|
| Invalid selection or descriptor | 400 |
validation error | Correct the repository selection. |
| Provider or repository not found | 404 |
not-found or validation error | Reconnect or select another repository. |
| Plugin inactive, timeout, rate limit, or upstream unavailable | 503 or typed retryable status |
unavailable error | Retry after the provider is available. |
The response includes a stable error code for plugin repository resolution and a bounded message. Logs can include workspace ID, provider ID, plugin ID, action key, duration, and failure category. Logs and client responses exclude secrets, authenticated URLs, plugin configuration, and raw response bodies.
Cancellation stops the action and returns without writes. A provider-specific failure occurs before the task-created event and before task-create last-used settings are recorded.
Compatibility
- A non-empty workspace
repository_idkeeps the current authorized lookup. - GitHub, GitLab, and Azure DevOps URLs keep the built-in parser.
- Plugin Host
Tasks.Createkeeps its authenticated trusted-descriptor path. - REST, WebSocket, and MCP callers cannot set the internal trust marker.
- A provider without a valid workspace-scoped
repositories.inspectaction cannot create a task from a first-use repository. Its already-persisted repositories remain usable. - Direct and nested repository action responses are accepted during the compatibility period. Public documentation defines the nested form as the preferred shape.
Responsive behavior
The feature uses the existing shared task dialog and repository picker. It adds no desktop-only control, compressed table, hover-only action, or new phone navigation. The same submit error keeps the dialog open on both presentations.
Playwright must prove the complete first-use flow in a desktop project and a phone project. The phone test also verifies that the repository and branch controls remain reachable, the submit action has a touch-sized target, and the dialog has no horizontal overflow.
Observability
Structured logs record one inspection outcome with provider ID, plugin ID, workspace ID, duration, response shape, and typed failure category. Success logs do not include repository URLs. Existing task-created logs remain the evidence that persistence followed a successful preflight.
No new metric is required for the first implementation. If operational data shows repeated provider-specific failures, a later change can add bounded outcome counters without changing this contract.