mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 06:59:42 +02:00
feat(docker): attach a case to an already-running container
Docker cases could only run in a container Codeman created itself. Attaching to one the user already built and runs means Codeman must leave that container's lifecycle completely alone, which the launch chain could not do: it was `image inspect` -> `inspect || create` -> `start` -> `exec`. Adds `DockerCase.owned`, mirroring the `owned:false` contract remote-SSH already uses for attached sessions. Absent (every existing case) means owned, so current behaviour is byte-identical. `false` means the container belongs to the user and Codeman may only exec into it. The launch chain for an attached container only looks, then execs: no image gate (the image is theirs), no create, and no `start` — starting a container we do not own is the very mutation attaching promises not to perform. A missing or stopped container fails closed with an actionable message instead. Credential seeding is skipped too: those copies read from create-time read-only mounts that do not exist here, and writing host credentials into someone's container is not ours to do, so its CLIs must already be authenticated inside it. Four fail-closed guards. buildDockerStopCommand and buildDockerRemoveCommand throw during pure string construction, so no caller bug can turn into a `docker stop`/`rm` on a container we do not own; removeDockerContainer refuses again at the lowest layer; drift reports "none" for an attached container, which carries no `codeman.confighash` label and would otherwise always look drifted and 409 the launch gate forever; and the orphan reaper skips attached containers through a check deliberately independent of the two conditions already covering them. `owned` is applied AFTER the config hash is computed. dockerConfigHash takes an explicit field list, so ownership can never shift an existing case's hash — if it did, every pre-existing case would trip the drift gate at once, and the remedy the UI offers is "recreate the container". Adds POST /api/cases/docker-adopt and a read-only POST /api/docker-cases/adopt-preflight. The preflight refuses at LINK time rather than at session launch, where the only ways out would be a dead pane or starting a container we do not own. Tests assert the negative guarantee directly — that create, start, stop, rm, restart and kill are absent from the generated commands while `docker exec -it` and `new-session -A` remain — since it cannot be observed by using the feature.
This commit is contained in:
@@ -243,6 +243,24 @@ export interface DockerCase {
|
||||
containerWorkdir?: string;
|
||||
/** Container name (default codeman-case-<slug>). */
|
||||
container?: string;
|
||||
/**
|
||||
* Whether THIS Codeman created the container (mirror of `SessionRemote.owned`).
|
||||
*
|
||||
* - `true` (default for cases Codeman linked/quick-created): we own the
|
||||
* container; drift may recreate it, case-delete may `docker rm -f` it, and
|
||||
* the launch chain may create + start it.
|
||||
* - `false` (ADOPTED: an already-running container the user built and runs
|
||||
* themselves): Codeman must never create, start, stop, restart or remove it.
|
||||
* The launch chain fails closed when the container is missing or not running
|
||||
* instead of touching its lifecycle, drift is not evaluated (there is no
|
||||
* `codeman.confighash` label to compare), and no credential seed is copied
|
||||
* into its HOME. Only the in-container tmux session is ever created or
|
||||
* killed — exactly the `owned:false` remote-SSH contract.
|
||||
*
|
||||
* Absent is treated as owned (cases persisted before this field existed were
|
||||
* all created by us).
|
||||
*/
|
||||
owned?: boolean;
|
||||
/** Last captured Claude conversation id, replayed via --resume on a fresh launch. */
|
||||
lastClaudeSessionId?: string;
|
||||
}
|
||||
@@ -275,6 +293,12 @@ export interface SessionDocker {
|
||||
extraExecArgs?: string[];
|
||||
/** Stable hash of the drift-relevant create args (recreate-on-drift detection). */
|
||||
configHash?: string;
|
||||
/**
|
||||
* Mirror of `DockerCase.owned`, flattened onto the live session so every
|
||||
* lifecycle decision (launch chain, drift, stop, remove) can see it without
|
||||
* re-reading docker-cases.json. Absent = owned. See `DockerCase.owned`.
|
||||
*/
|
||||
owned?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user