Files
kandev/docs/plans/task-git-credential-policy/plan.md
T
2026-07-27 18:10:12 +01:00

273 lines
15 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
### Task Git credentials settings
- Extend `GitHubWorkspaceSettings` and `UpdateGitHubWorkspaceSettingsRequest` in
`apps/web/lib/types/github.ts`.
- Add `apps/web/components/github/github-task-credentials-section.tsx`, registered with
`useSettingsSaveContributor`, with two explicit choices:
**Managed workspace credentials** and **Inherit executor Git credentials**.
- Visible copy must explain local/Worktree host behavior, remote executor behavior, profile-token
precedence, and when each choice applies. Reuse the repository-scope responsive help pattern:
tooltip/focus on a fine pointer and a 44px information-button Drawer on a coarse pointer.
- 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 information disclosure supplements rather than replaces this text.
### 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:** new component/model tests beside
`apps/web/components/github/github-task-credentials-section.tsx`.
**How:** mocked API promises and settings-save provider; keyboard-focus help assertion.
- **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 switches to executor inheritance and saves,
THEN the API persists `executor`; discard/reload and switching back to managed remain coherent,
and the desktop help names PAT/CLI/App delivery and profile-token precedence.
**File:** extend
`apps/web/e2e/tests/integrations/github-workspace-settings.spec.ts`.
- **Scenario:** GIVEN a coarse-pointer settings viewport, WHEN the task-credential information icon
is tapped, THEN a Drawer contains the same delivery explanation, controls meet 44px targets, and
the page has no horizontal overflow.
**File:** extend
`apps/web/e2e/tests/integrations/mobile-github-auth-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 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)
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.