133 lines
8.3 KiB
Markdown
133 lines
8.3 KiB
Markdown
# ADR-2026-08-12-task-bound-fork-destinations: Bind Fork Push Destinations to Tasks
|
|
|
|
**Status:** accepted (amended 2026-08-13)
|
|
**Date:** 2026-08-12
|
|
**Area:** backend, frontend, workflow, security, GitHub
|
|
|
|
## Context
|
|
|
|
A contribution task created against a canonical GitHub repository has two repository identities before
|
|
a pull request exists. The canonical repository owns issues, the future pull request, the base branch,
|
|
and Kandev's repository row. A contributor-owned fork is only the destination for publishing the task
|
|
branch.
|
|
|
|
Replacing the repository row or `origin` with the fork makes issue lookup and pull-request detection use
|
|
the wrong repository. Keeping the canonical identity without recording the fork makes managed task Git
|
|
credentials reject the push, because their helper and `gh` shim accept only repository identities covered
|
|
by task-bound leases. Switching the workspace to executor-owned credentials would make the flow work, but
|
|
it can expose a host credential with access to unrelated repositories.
|
|
|
|
Kandev already solves the equivalent post-PR problem with a server-authored `remote_contribution`
|
|
binding. That binding keeps the target repository as `origin`, adds a dedicated source remote, and permits
|
|
one exact additional managed credential scope. A pre-PR contribution needs the same trust shape without
|
|
claiming that a pull request, source branch, or head SHA already exists.
|
|
|
|
Managed credential leases are fixed when the task runtime starts. Preparing a fork only when the task
|
|
enters its PR workflow step is therefore too late for a long-lived managed session unless the whole
|
|
environment is recreated. Managed fork resolution must complete before the first task launch.
|
|
|
|
Path-only metadata does not prove identity when the workspace automation connection changes, GitHub
|
|
deletes and recreates a repository at the same owner/name, or a fork is renamed. The destination contract
|
|
must bind both the credential identity and the provider-owned repository identities.
|
|
|
|
## Decision
|
|
|
|
Kandev will represent a pre-PR fork as a versioned, server-authored
|
|
`contribution_destination` binding on the canonical task-repository attachment. The binding contains only
|
|
the provider, the exact credential-free source repository identity and URL, provider-owned stable IDs, and
|
|
a non-secret binding to the workspace automation source and credential generation. The attachment's normal
|
|
`repository_id` remains the target identity. No token, lease, provider title, or caller-supplied Git remote
|
|
is trusted or persisted.
|
|
|
|
When the workspace selects managed task credentials, the Improve Kandev task-creation path resolves the
|
|
destination through the selected workspace automation connection before the task is settled or its first
|
|
agent starts. Direct target write access requires no second binding. Otherwise Kandev reuses or creates the
|
|
automation actor's fork, verifies that its parent provider ID and full name are exactly `kdlbs/kandev`,
|
|
verifies write access, and persists the binding. A same-name repository with a different parent fails closed.
|
|
|
|
Exact-name lookup is only the fast path. If it returns a provider 404, Kandev searches the canonical
|
|
repository's fork network and re-reads each same-owner candidate through the provider before accepting it.
|
|
This reuses renamed forks without trusting list payloads or creating a duplicate.
|
|
|
|
Fork ownership must match the identity that supplies managed task credentials. A human PAT or named GitHub
|
|
CLI automation connection may own and create a fork. A GitHub App may contribute directly when its
|
|
installation can write the target; otherwise automatic fork preparation fails with configuration guidance.
|
|
Kandev does not create a fork with a personal connection and then attempt to push it with an unrelated App
|
|
installation.
|
|
|
|
Runtime materialization preserves canonical `origin` and adds a collision-resistant dedicated remote for
|
|
the bound fork. The task branch tracks that remote for ordinary `git push`; pull, base comparison, issue
|
|
lookup, and pull-request creation continue to target the canonical repository explicitly. Managed GitHub
|
|
credentials add a second lease only when its owner/repository exactly matches a valid destination binding
|
|
on the same task attachment. The broker authorizer compares canonical, target, and parent provider IDs and
|
|
re-reads the target through the current workspace automation connection at both lease issuance and
|
|
redemption. A path reused by a deleted-and-recreated repository cannot inherit an old lease.
|
|
|
|
If task policy becomes executor-owned or an explicit executor `GH_TOKEN`/`GITHUB_TOKEN` is present, the
|
|
runtime clears any managed destination from the launch request and uses the separate unmanaged compatibility
|
|
path. A destination bound to another workspace connection generation, login, App installation, or App
|
|
credential generation is rejected or revoked.
|
|
|
|
The managed Improve Kandev PR path does not run `gh repo fork` or rename `origin`. It pushes `HEAD` through
|
|
the configured contribution remote and creates the pull request with explicit canonical `--repo`, base,
|
|
and fork-owner/head arguments. Launch, resume, and managed-origin reconciliation reconstruct this
|
|
arrangement idempotently.
|
|
|
|
Executor-owned task credentials remain an explicit unmanaged compatibility path. Kandev does not infer or
|
|
persist a fork from an opaque local, SSH, container, or cloud executor identity. The PR workflow may retain
|
|
its existing agent-managed fork setup only when no managed destination is present and the workspace policy
|
|
is executor-owned; it must never use that fallback after a managed preparation failure.
|
|
|
|
## Consequences
|
|
|
|
- GitHub issue and pull-request integration continue to use `kdlbs/kandev`.
|
|
- Managed mode exposes at most the canonical repository plus one server-verified fork to the task, instead
|
|
of the executor's ambient GitHub authority.
|
|
- Fork resolution or creation becomes required synchronous task-creation work for Improve Kandev
|
|
implementation tasks that use managed credentials. Failure leaves no partly launched task.
|
|
- The binding and remote setup must be projected through every supported executor and reconstructed on
|
|
resume.
|
|
- Bootstrap capability reporting must use the workspace automation connection, not ambient host `gh`, so
|
|
the displayed actor and the eventual managed credential source cannot disagree in managed mode. The
|
|
executor-owned compatibility probe remains explicitly separate.
|
|
- Bootstrap persists the canonical provider repository ID when it can resolve it. New blocked states cross
|
|
the API as stable `fork_reason_code` values; the frontend translates those codes instead of displaying
|
|
backend-authored English text.
|
|
- Existing `remote_contribution` bindings remain the authority for tasks attached to an already-open pull
|
|
request. The new binding covers only the period before a pull request exists.
|
|
- User-managed local repository rows retain their existing remote exemption. Kandev-managed provider rows
|
|
continue to reconcile `origin` to canonical HTTPS.
|
|
|
|
## Alternatives considered
|
|
|
|
### Replace `origin` with the contributor fork
|
|
|
|
Rejected. It makes the fork look canonical and breaks issue lookup, pull-request detection, base fetches,
|
|
and managed-checkout reconciliation.
|
|
|
|
### Allow any fork of the canonical repository at credential time
|
|
|
|
Rejected. Discovering a relationship at lease redemption is less deterministic, makes authorization depend
|
|
on a live provider query, and can silently broaden a task after creation. The exact destination must be
|
|
server-verified and task-bound before launch.
|
|
|
|
### Prepare the fork when entering the PR step
|
|
|
|
Rejected. Existing sessions keep their launch-time credential snapshot. A context reset does not reliably
|
|
rebuild the task environment or issue new leases.
|
|
|
|
### Attach the fork as a second task repository
|
|
|
|
Rejected. The fork is not a second workspace source. Materializing it independently would duplicate the
|
|
checkout and confuse repository ownership, diffs, and change-request association.
|
|
|
|
### Require executor-owned credentials for fork contributors
|
|
|
|
Rejected. It restores functionality by abandoning the managed trust boundary and may expose credentials
|
|
for unrelated personal or work repositories.
|
|
|
|
### Pre-bind an executor-owned fork from workspace automation
|
|
|
|
Rejected. Executor credentials can belong to a different host or account and are intentionally opaque to
|
|
Kandev. A workspace-authored fork would not prove that the executor can write it.
|