launchpolicy

package
v0.187.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: BSD-2-Clause Imports: 1 Imported by: 0

Documentation

Overview

Package launchpolicy materialises, exactly once per daemon process, the set of launch-shape inputs that are intrinsic to "this is a dark-factory container": image, project identity, mounts, base environment, netrc/gitconfig paths, hide-git, and the canonical Linux capability set (NET_ADMIN, NET_RAW).

Two call sites consume it: the executor's prompt-run path (pkg/executor.dockerExecutor.buildDockerCommand) and the healthcheck probes' container-launch path (pkg/cmd/healthcheck.runContainerProbe). Both derive their executor.ContainerLaunchOpts from the same Policy value via Policy.BuildOpts; per-invocation differences (container name, entrypoint, command, env overlay, label overlay) flow through the Extras argument.

A reader who wants to add a new launch-shape concern (new cap, new mount, new base env var) adds it here once. Both the executor and the probes pick it up with no further changes. The canonical container-startup-site inventory is maintained in the comment block on Policy itself; the inventory's enumerated set of file:line pairs must equal the UNION of:

grep -rnE 'exec\.Command(Context)?\(.*"docker"' pkg/ | grep -v _test.go
grep -rnE 'RunWithWarnAndTimeout\([^,]+,[^,]+,[^,]+"docker"' pkg/ | grep -v _test.go

(Dark-factory invokes `docker` two ways: directly via `exec.Command*` and indirectly via `subproc.Runner.RunWithWarnAndTimeout`. The inventory must cover both. Any drift between the inventory and the union of the two grep outputs is an unresolved architectural divergence and must be fixed in the same change that introduced it.)

Index

Constants

This section is empty.

Variables

View Source
var CanonicalCaps = []string{"NET_ADMIN", "NET_RAW"}

CanonicalCaps is the Linux capability set every dark-factory container requires. NET_ADMIN + NET_RAW are needed by the claude-yolo entrypoint's init-firewall.sh (iptables rules) to run on container backends that reject those syscalls without explicit grants (e.g. OrbStack).

This is the SINGLE production reference to the capability literals in the repository. The architectural invariant

grep -rn "NET_ADMIN" pkg/ | grep -v _test.go | wc -l

MUST return exactly 1 (this file). Adding the literals anywhere else re-introduces spec-098's divergence-by-construction.

Functions

This section is empty.

Types

type ContainerLaunchOpts

type ContainerLaunchOpts struct {
	// ContainerName is the value passed to `--name`.
	ContainerName string
	// ContainerImage is the image reference (positional, last before Command).
	ContainerImage string
	// ProjectName is the value of the `dark-factory.project=` label.
	ProjectName string
	// ProjectRoot is the host path mounted at /workspace and the base used by
	// HideGit + ExtraMounts path resolution.
	ProjectRoot string
	// ClaudeDir is the host path mounted at /home/node/.claude (auth credentials).
	ClaudeDir string
	// Home is the host's HOME, used for ~/ expansion in NetrcFile/GitconfigFile/ExtraMounts.
	Home string
	// Env is appended as -e KEY=VALUE flags, sorted by key for stable argv shape.
	Env map[string]string
	// ExtraMounts is appended as -v <src>:<dst>[:ro] flags; missing src is skipped + logged.
	ExtraMounts []config.ExtraMount
	// NetrcFile, when non-empty, is mounted at /home/node/.netrc:ro.
	NetrcFile string
	// GitconfigFile, when non-empty, is mounted at /home/node/.gitconfig-extra:ro.
	GitconfigFile string
	// HideGit, when true, masks ProjectRoot/.git inside the container.
	HideGit bool
	// ExtraLabels is appended as additional --label KEY=VALUE flags after the project label.
	ExtraLabels map[string]string
	// CapAdd is appended as --cap-add=<value> flags.
	CapAdd []string
	// Entrypoint, when non-empty, is passed as --entrypoint <value>.
	Entrypoint string
	// Command is appended after the image (positional args to the container).
	Command []string

	// RunAsUser, when non-empty, is passed as --user <value>. Empty means
	// container runs as the image's default user (typically root).
	RunAsUser string
	// MemoryLimit, when non-empty, is passed as --memory <value> (e.g. "8g").
	MemoryLimit string
	// CPULimit, when non-empty, is passed as --cpus <value> (e.g. "4").
	CPULimit string
	// PIDsLimit, when > 0, is passed as --pids-limit <N>. Values <= 0 are
	// treated as "unset" — zero is the field's zero value used as the
	// "unset" sentinel; negative values are silently dropped (docker would
	// reject them anyway and the sentinel semantics are simpler than
	// returning an error from BuildDockerRunArgs).
	PIDsLimit int
	// ClaudeDirReadOnly, when true, mounts ClaudeDir with :ro suffix. Default
	// false preserves rw mount needed for OAuth token refresh.
	ClaudeDirReadOnly bool
}

ContainerLaunchOpts carries the inputs needed to assemble a `docker run --rm ...` argv for any dark-factory container — prompt execution, spec generation, or healthcheck probes. Centralising the argv build here keeps the production launch path and the healthcheck probes on the same mount/env/hideGit/extraMounts wiring; if production stops launching containers correctly, the healthcheck probes notice immediately.

type Extras

type Extras struct {
	// ContainerName is the value passed to --name. Required.
	ContainerName string
	// Entrypoint is passed as --entrypoint <value> when non-empty.
	Entrypoint string
	// Command is appended after the image (positional container args).
	Command []string
	// EnvOverlay is merged into the Policy's base env. Keys in EnvOverlay
	// win on collision with baseEnv — the executor's prompt-specific values
	// (YOLO_PROMPT_FILE, YOLO_OUTPUT, ANTHROPIC_MODEL) are passed this way.
	EnvOverlay map[string]string
	// ExtraLabels is appended as --label KEY=VALUE flags after the project
	// label. Used e.g. by the executor's "dark-factory.prompt=<basename>"
	// label. Empty / nil leaves no extra labels.
	ExtraLabels map[string]string
}

Extras carries the per-invocation inputs that differ between the executor's prompt-run and the healthcheck probes' one-shot containers. Everything in Extras is layered ON TOP of the Policy's base launch shape.

type Policy

type Policy struct {
	// contains filtered or unexported fields
}

Policy carries the launch-shape inputs intrinsic to every dark-factory container. Constructed once per daemon process (or per command invocation) from config + environment; consumed by both the executor's prompt-run path and the healthcheck probes.

All fields are unexported. Construct via NewPolicy and consume via BuildOpts.

CONTAINER-STARTUP-SITE INVENTORY (spec 098 AC "Container-startup-site inventory is complete"). Every production-code site that invokes `exec.Command(ctx, "docker", ...)` OR `subproc.Runner.RunWithWarnAndTimeout(ctx, op, "docker", ...)` in pkg/ is classified below. The enumerated set MUST equal the UNION of

grep -rnE 'exec\.Command(Context)?\(.*"docker"' pkg/ | grep -v _test.go
grep -rnE 'RunWithWarnAndTimeout\([^,]+,[^,]+,[^,]+"docker"' pkg/ | grep -v _test.go

at HEAD. CI may grep this comment to detect drift.

Routed through Policy.BuildOpts (docker run / claude-yolo containers):

pkg/executor/executor.go:517   exec.CommandContext(ctx, "docker", args...)
                               -- prompt-run path (buildDockerCommand)
                               -- also serves spec generation via the
                                  shared executor.NewDockerExecutor
pkg/cmd/healthcheck/probes.go:319 a.runner.RunWithWarnAndTimeout(
                                    ctx, a.op, "docker",
                                    executor.BuildDockerRunArgs(opts)...)
                               -- boot / mount / claude probes

Explicitly out of scope (do NOT invoke claude-yolo; carry no caps, no mounts, no /workspace bind):

pkg/cmd/healthcheck/probes.go:115  "docker version"
pkg/cmd/healthcheck/probes.go:150  "docker image inspect --format=..."
pkg/executor/checker.go:72         "docker inspect --format ..."
pkg/executor/checker.go:105        "docker ps ..." (NewDockerContainerChecker)
pkg/executor/executor.go:244       "docker logs --follow <name>"
pkg/executor/executor.go:298       "docker stop <name>"
pkg/executor/executor.go:303       "docker kill <name>"
pkg/executor/executor.go:352       "docker stop <name>"
pkg/executor/executor.go:600       "docker stop <name>"
pkg/executor/executor.go:611       "docker rm -f <name>"
pkg/executor/stopper.go:32         "docker stop <name>"
pkg/status/status.go:546           "docker ps --filter ..."
pkg/status/status.go:569           "docker ps --filter ..."

Out-of-scope rationale: these sites do not start a claude-yolo container; they query/inspect/stop/kill/log existing containers. They have no mount, env, capability, or hide-git surface.

func NewPolicy

func NewPolicy(
	containerImage string,
	projectName string,
	projectRoot string,
	claudeDir string,
	home string,
	baseEnv map[string]string,
	extraMounts []config.ExtraMount,
	netrcFile string,
	gitconfigFile string,
	hideGit bool,
) Policy

NewPolicy returns a Policy capturing the launch-shape inputs from cfg + the resolved process environment (home, projectRoot). capAdd is initialised to CanonicalCaps; callers cannot override (see spec 098 Non-goal "Do NOT make capabilities configurable").

projectName is the value of the dark-factory.project label. projectRoot is the host path mounted at /workspace. home is the host HOME, used for ~/ expansion in mount paths. baseEnv is the operator-configured env map (cfg.Env) plus daemon-injected values such as ANTHROPIC_MODEL. Prompt-specific keys (YOLO_PROMPT_FILE, YOLO_OUTPUT) are NOT part of the base — they are passed in via Extras so unrelated invocations stay clean.

func (Policy) BaseEnv

func (p Policy) BaseEnv() map[string]string

BaseEnv returns a shallow copy of the base environment map. Callers that only read the map may use the returned value directly; callers that mutate it should copy first.

func (Policy) BuildOpts

func (p Policy) BuildOpts(extras Extras) ContainerLaunchOpts

BuildOpts returns a ContainerLaunchOpts ready for executor.BuildDockerRunArgs. The returned value carries the policy's base launch shape plus the per-invocation extras.

THIS IS THE ONLY production-code site that composes ContainerLaunchOpts{...} after spec 098 lands. The architectural invariant

grep -rn "ContainerLaunchOpts{" pkg/ | grep -v _test.go | wc -l

MUST return exactly 1 (this method).

func (Policy) ClaudeDir

func (p Policy) ClaudeDir() string

ClaudeDir returns the resolved claude config directory (host path).

func (Policy) ContainerImage

func (p Policy) ContainerImage() string

ContainerImage returns the image reference (consumed by callers needing it outside BuildOpts, e.g. the executor's insertPromptFileMount).

func (Policy) ProjectName

func (p Policy) ProjectName() string

ProjectName returns the value of the dark-factory.project label.

func (Policy) WithCapAddForTest

func (p Policy) WithCapAddForTest(caps []string) Policy

WithCapAddForTest returns a copy of p with capAdd replaced by caps. Test-only override; production callers cannot vary the cap set (spec 098 Non-goal). The method name carries "ForTest" so reviewers and tooling can flag any non-test caller as a violation.

func (Policy) WithSecurity added in v0.184.0

func (p Policy) WithSecurity(opts SecurityOpts) Policy

WithSecurity returns a copy of p with the security fields REPLACED by opts. Policy is a value type and this method uses a value receiver: assignments to p inside this body mutate a local copy, not the caller's Policy. Mirrors WithCapAddForTest's immutable-builder pattern.

Replace (not merge) semantics: a zero-value SecurityOpts{} explicitly resets every security field on the returned policy.

Phase 2 (separate PR) will call this from the factory with non-zero defaults after dev validation confirms ro claudeDir works for Claude SDK auth refresh.

type SecurityOpts added in v0.184.0

type SecurityOpts struct {
	RunAsUser         string
	MemoryLimit       string
	CPULimit          string
	PIDsLimit         int
	ClaudeDirReadOnly bool
}

SecurityOpts groups the security hardening fields applied via WithSecurity. Defaults (zero values) preserve current production behavior (root, rw credentials, no resource limits). See ADR-0001 for the rollout plan.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL