Files
kandev/docs/plans/task-git-credential-policy/plan.md
T
2026-07-31 19:20:49 +01:00

298 lines
17 KiB
Markdown

---
spec: docs/specs/integrations/github-authentication.md
created: 2026-07-27
status: complete
---
# Implementation Plan: Task Git Credential Policy And Identity
## Overview
Fix the confirmed standalone managed-helper wiring failure while separating workspace GitHub
automation from task Git credential routing. Persist the policy first, then make initial launch and
resume resolve the same effective contract and record a truthful session snapshot. Finish with
self-documenting settings, responsive Changes-panel identity disclosure, focused E2E coverage, and
public documentation.
Confirmed root causes:
1. The standalone launcher resolves and starts the `agentctl` control binary by absolute path, but
the per-task Git config stores `!agentctl git-credential`. The control process only
creates/prepends its `gh` shim directory when broker environment exists at control-process
startup; standalone broker data arrives later in the per-instance environment. Therefore child
Git cannot find `agentctl`.
2. Task environments currently merge `GIT_CONFIG_COUNT` and indexed keys as ordinary map entries.
A Kandev block with count 2 overwrites a host/executor block's count and first two entries, so
unrelated tools such as hooks and Git notes disappear. The same defect can hide Docker's
`safe.directory` and URL rewrites. A focused reproduction against `CollectAgentEnv` produced
count 2 where the correct composed result is count 4.
3. Resume currently does not rebuild the broker contract at all.
---
## Backend
### Ordered indexed Git configuration
- Add a shared backend utility for parsing, validating, composing, and contiguously serializing the
`GIT_CONFIG_COUNT` / `GIT_CONFIG_KEY_<n>` / `GIT_CONFIG_VALUE_<n>` protocol.
- Composition preserves source order, appends higher-precedence entries, and removes only the
longest exact suffix/prefix overlap when the same block has already passed through a control
process. It never deduplicates arbitrary repeated Git keys because repeated keys can be
meaningful.
- Use structured composition in agent-profile/request merges, route overrides, container
environment construction, remote forwarding, and agentctl's parent-plus-instance collection.
Ordinary scalar environment variables retain their documented precedence.
- Keep `internal/repoclone` isolated: managed clone subprocesses intentionally strip ambient Git
configuration and build a closed credential environment.
- Add a real Git subprocess regression that starts with locstat-shaped `core.hooksPath` and
`notes.augment.mergeStrategy` entries, adds Kandev's helper entries, makes a commit, and verifies
the hook ran. Add focused standalone, Docker, SSH/remote, overlap, precedence, malformed-block,
and bounded-count tests.
### Workspace policy persistence and description
- In `apps/backend/internal/github/models.go`, add
`TaskGitCredentialsModeManaged`, `TaskGitCredentialsModeExecutor`, and
`WorkspaceSettings.TaskGitCredentialsMode` /
`UpdateWorkspaceSettingsRequest.TaskGitCredentialsMode`.
- In `apps/backend/internal/github/store.go`, add
`github_workspace_settings.task_git_credentials_mode TEXT NOT NULL DEFAULT 'managed'` to fresh
schema and a fail-loud, idempotent startup migration. Include it in defaults, normalization,
reads, upserts, partial patches, and workspace-settings copy.
- In `apps/backend/internal/github/workspace_settings_service.go`, reject unknown policy values
without mutating the row.
- Add a non-secret service description that combines the policy with
`github_workspace_connections` and, for Apps, registration display identity. It returns the
selected mode, method, and known actor context without resolving or exposing a credential.
- Extend the existing `/api/v1/github/workspace-settings` response/update contract. Authorization
remains the existing workspace-settings authorization path.
### Launch/resume resolution and session snapshot
- In `apps/backend/internal/task/models/models.go`, add
`SessionMetaKeyGitCredentialSnapshot`, a versioned `GitCredentialSnapshot` type, and a
JSON-rehydration-safe loader.
- In `apps/backend/internal/orchestrator/executor/executor.go` and
`executor_credentials.go`, add a task Git credential policy resolver wired beside
`GitHubCredentialLeaseIssuer`. Resolve one of:
`workspace`, `executor_profile`, or `executor`.
- Move effective GitHub credential configuration out of the middle of
`buildLaunchAgentRequest` so it runs after profile/request environment is final. Reuse the same
function from `buildResumeRequest` after `resolveAllRepoInfo`.
- For managed workspace credentials, clear stale broker keys, reset the GitHub HTTPS helper chain
with an empty helper before Kandev's helper, set `credential.useHttpPath=true`, and disable
terminal prompts. Executor inheritance removes/omits Kandev broker/helper/shim configuration.
- Put the candidate snapshot on the in-memory session before lifecycle launch, but persist it only
through the existing successful `persistLaunchState` / `persistResumeState` full-row write. A
failed operation leaves the prior durable snapshot unchanged.
- In `apps/backend/internal/backendapp/orchestrator.go`, adapt the GitHub service's non-secret
policy descriptor into the executor interface alongside the existing lease adapter.
### Managed runtime tools and CLI refresh
- Refactor `apps/backend/cmd/agentctl/github_cli_shim.go` so the control process always creates one
private managed GitHub tool directory containing both `gh` and `agentctl` links/copies, records
its path, and does not globally prepend it for every instance.
- In `apps/backend/internal/agentctl/server/config/config.go`, prepend that directory only when the
instance's additional environment contains the complete broker contract. All agent processes,
shells, background commands, and agentctl Git operations then share the same per-instance PATH.
- Preserve `runGitHubCLIShim` lookup of the real `gh` outside the managed directory. Executor
inheritance and profile-token override instances must see the normal executor PATH.
- Add a real subprocess regression around the standalone shape: control `agentctl` starts without
broker env, a broker-enabled instance receives it later, and both `git credential fill` and
`gh` resolve through the managed tools. Missing broker/helper fails instead of consulting an
ambient helper.
- In `apps/backend/internal/github/auth_resolver.go`, give named-CLI cached credentials a separate
five-minute cache deadline without pretending the provider token itself has an expiry.
Generation invalidation remains immediate.
---
## Frontend
### Unified GitHub access configuration
- Extend `GitHubWorkspaceSettings` and `UpdateGitHubWorkspaceSettingsRequest` in
`apps/web/lib/types/github.ts`.
- Refactor `apps/web/components/github/github-task-credentials-section.tsx` from a standalone
settings section into reusable task-access summary and dialog controls with two explicit choices:
**Managed workspace credentials** and **Inherit executor Git credentials**. The dialog owns the
draft, failure feedback, and saved baseline.
- Visible copy must explain local/Worktree host behavior, remote executor behavior, profile-token
precedence, and when each choice applies. Supplementary responsive help explains the injected
`agentctl` Git credential helper, scoped broker lease, broker-aware `gh` shim, and launch/resume
timing without exposing credentials.
- Update `github-auth-method-list.tsx`, `github-connection-dialog.tsx`, and `github-settings.tsx`
so PAT, named CLI, and App cards state where their credential is stored/resolved and how managed
tasks receive it; the task policy controls live below the method controls in the same bounded
dialog/drawer. Remove the standalone Task Git credentials section from the settings page.
- Replace the PAT, CLI, and task-policy submission buttons with one dialog-level **Save changes**
action in a fixed bottom row that persists every changed local draft. Keep one scrolling content
region above it, add a bottom fade as an overflow cue, place the GitHub CLI card first, and use
compact spacing between task-access options. App create/import/install remain workflow actions
because they navigate through GitHub instead of saving a local credential draft.
- Update `github-status.tsx` so the Workspace GitHub access summary shows the current task access
mode beside the automation identity and refreshes after a successful dialog submission.
- Desktop retains a bounded but wider dialog. Mobile retains the existing full-height Drawer, one
internal scroll owner, a safe-area-aware fixed footer, 44px controls, and no horizontal overflow.
### Changes branch credential disclosure
- Add a pure parser/view-model module beside the Changes panel that reads
`git_credential_snapshot` from the active `TaskSession.metadata`, tolerates malformed/legacy
values, and produces display labels without guessing unknown actors.
- Extract the current branch-card body from
`apps/web/components/task/changes-panel-header.tsx`. Keep the compact fine-pointer trigger, but
make it keyboard-focus accessible and include policy, effective method/source, actor, and
transport below the existing branch/base-branch rows.
- Use `useTouchDrawer` for coarse pointers. The mobile trigger is at least 44px and opens a Drawer
containing the same branch editing/base-branch controls and credential truth; desktop keeps the
hover/focus card.
- Select the active session snapshot in `useChangesPanelData` and pass the normalized disclosure
through both desktop `changes-panel.tsx` and `mobile/mobile-changes-panel.tsx`.
---
## Tests
- **What:** fresh/replayed schemas default existing and new workspaces to `managed`; policy
round-trips, partial updates reject invalid values, and settings copy includes policy but not
authentication.
**File:** `apps/backend/internal/github/workspace_settings_test.go`,
`copy_test.go`, and focused migration tests.
**How:** real SQLite store/service tests, including same-database schema replay.
- **What:** the internal policy description identifies PAT/CLI humans and App registration context
without resolving tokens, while executor actors remain unknown.
**File:** focused tests under `apps/backend/internal/github/`.
**How:** table-driven service tests with fake connections/registration store.
- **What:** initial launch and resume produce the same managed/executor/profile-override contract;
managed helper chains reset, inheritance injects nothing, and only successful lifecycle launches
persist a new session snapshot.
**File:** `apps/backend/internal/orchestrator/executor/executor_credentials_test.go`,
`executor_resume_test.go`, and launch-path tests.
**How:** table-driven unit tests plus mocked lifecycle success/failure integration tests.
- **What:** host/executor/profile/task indexed Git config blocks compose in precedence order,
already-forwarded suffixes occur once, malformed blocks fail clearly, and unrelated settings
remain active beside Kandev's managed helper.
**File:** new shared utility tests,
`apps/backend/internal/agentctl/server/config/config_test.go`,
`apps/backend/internal/agent/runtime/lifecycle/executor_docker_test.go`, and focused remote tests.
**How:** table-driven block composition plus a temp-repository real Git commit/hook subprocess
regression using locstat-shaped entries.
- **What:** standalone per-instance broker activation makes `agentctl` and `gh` discoverable even
though the control process started without broker env; inherit/override instances do not activate
the tool directory.
**File:** `apps/backend/cmd/agentctl/github_cli_shim_test.go`,
`apps/backend/internal/agentctl/server/config/config_test.go`.
**How:** temp executable/subprocess and fake HTTP broker tests; invoke real Git credential
plumbing where supported.
- **What:** named CLI cache entries re-resolve after five minutes and still invalidate immediately
on generation changes.
**File:** `apps/backend/internal/github/auth_resolver_test.go`.
**How:** deterministic fake clock and call counter.
- **What:** settings drafts load, save, discard, and explain both policies and every automation
method.
**File:** `apps/web/components/github/github-connection-dialog.test.tsx` and focused tests beside
`apps/web/components/github/github-task-credentials-section.tsx` if state is extracted.
**How:** mocked workspace-settings API promises; verify compact summary, explicit dialog save,
close/discard behavior, refresh, and failure retention.
- **What:** valid/malformed/missing session snapshots produce truthful disclosure labels, never an
inferred actor.
**File:** new Changes credential view-model test and focused header component test.
**How:** table-driven Vitest plus keyboard-focus component test under `TooltipProvider`.
Targeted pre-PR commands are recorded in the task files.
---
## E2E Tests
- **Scenario:** GIVEN workspace settings, WHEN the user sees the compact Task access summary, opens
Change GitHub connection, switches to executor inheritance, and explicitly saves, THEN the API
persists `executor`, the summary updates, reopening retains the saved mode, and the automation
identity remains unchanged.
**File:** extend
`apps/web/e2e/tests/integrations/github-workspace-settings.spec.ts`.
- **Scenario:** GIVEN a coarse-pointer settings viewport, WHEN Change GitHub connection opens, THEN
its full-height Drawer contains the task access controls in the same single scroll body, the
controls meet 44px targets, saving updates the page summary, and neither surface has horizontal
overflow.
**File:** extend
`apps/web/e2e/tests/integrations/mobile-github-workspace-settings.spec.ts`.
- **Scenario:** GIVEN a session seeded with a managed CLI or App snapshot, WHEN a desktop user
hovers/focuses the Changes branch trigger, THEN branch/base information and launch-time actor,
method, and managed transport are visible.
**File:** `apps/web/e2e/tests/git/git-credential-identity.spec.ts`.
- **Scenario:** GIVEN an inherited or profile-token snapshot on a coarse-pointer task view, WHEN the
branch trigger is tapped, THEN a Drawer labels the actor runtime-selected, has a 44px trigger, and
does not overflow.
**File:** `apps/web/e2e/tests/git/mobile-git-credential-identity.spec.ts`.
Production E2E runs only after rebuilding backend and Vite artifacts.
---
## Public Documentation
- Update `docs/public/integrations.md`, `docs/public/executors.md`, and
`docs/public/use-kandev.md` to separate workspace automation method from task credential policy,
describe named CLI brokerage, explain executor inheritance, and document the Changes snapshot.
- Update `docs/public/integrations.md` so users find task access inside **Change GitHub connection**
and understand the compact Workspace GitHub access summary; do not describe a standalone Task Git
credentials section.
- Update troubleshooting so `agentctl: command not found` is a managed-runtime defect rather than a
prompt to fall back to personal SSH, and distinguish managed-helper failure from missing executor
credentials.
---
## Implementation Waves And Parallel Candidates
Wave 1:
- [x] [task-00-indexed-git-config-composition](task-00-indexed-git-config-composition.md)
- [x] [task-01-policy-persistence](task-01-policy-persistence.md)
Wave 2 (parallel candidates — user authorization required):
- [x] [task-02-launch-resume-snapshot](task-02-launch-resume-snapshot.md)
- [x] [task-04-settings-explanation](task-04-settings-explanation.md)
Wave 3 (parallel candidates — user authorization required):
- [x] [task-03-managed-runtime-tools](task-03-managed-runtime-tools.md)
- [x] [task-05-changes-identity-disclosure](task-05-changes-identity-disclosure.md)
Wave 4:
- [x] [task-06-e2e-and-documentation](task-06-e2e-and-documentation.md)
Wave 5:
- [x] [task-07-unified-github-access-settings](task-07-unified-github-access-settings.md)
The default execution is sequential in this primary conversation. Waves identify dependency-safe
opportunities only and do not authorize subagents.
## Risks
- Local and Worktree agents retain host process authority. Managed routing can fail closed for
Kandev-injected GitHub HTTPS/`gh`, but cannot prevent a capable agent from selecting SSH or
another host tool.
- The helper-reset entry must be ordered before Kandev's helper without deleting unrelated
caller-supplied Git config.
- Indexed Git config keys form one structured protocol. Plain map overlay at any launch boundary
can regress host tooling; arbitrary de-duplication can also change the meaning of multi-valued
Git settings.
- Docker and remote control processes can already contain the task suffix before it is forwarded
to an instance, so composition must remove only an exact boundary overlap.
- Resume must resolve every attached repository, not only the legacy primary fields.
- The snapshot is launch-time truth. Rendering current workspace status in its place would
reintroduce misleading identity.
- Agentctl tool-directory activation must be per instance so executor inheritance and explicit
profile-token overrides do not accidentally invoke the broker shim.
- Workspace connection and task policy remain separate persistence operations inside one dialog.
The single submission must report complete success only when every changed operation succeeds,
refresh any partial success, and preserve failed or unsaved drafts for retry.