FL-09 — Services, Docker, Compose & processes · Task breakdown
Ordered, executable tasks derived from
plan.md. Each carries explicit file paths and a test. Every task ships with its tests (Constitution VI). Work top to bottom; tasks marked (parallel) may run alongside the task immediately above them.
Feature ID: FL-09-services-docker
Spec: ./spec.md · Plan: ./plan.md
Status: Draft
Last updated: 2026-10-18
How to use
- Paths are repo-relative to the monorepo root;
pnpmfrom the root. - Runners:
packages/agent,apps/api→ Jest;apps/node,packages/contracts,apps/mcp,apps/webunit → Vitest;apps/webe2e → Playwright. - Prerequisites: FL-02 (link client, module contract, operation runner,
NodeLinkService, gateway), FL-01 (registerComputerTab,registerOverviewBlock,assertFamily), FL-08 (apps/node/src/core/link/allowed-roots.ts,FleetMachineFactsEnabledGuard, log deep links), FL-04 for P3 exec Sessions. Adapt their merged names in one place each. - No migration in this epic (no FL-09 slot in CONTRACTS §13).
- Never pass a user-supplied name through a shell string; every child gets an argv array. The lint rule in T-05 enforces it.
- Add new tasks at the bottom; never renumber. Commits:
feat(fleet-machine): …,feat(fleet-docker): …,test(fleet-machine): …,chore(i18n): ….
PHASE 1 — Services and processes
P1.A — Contracts
- T-01. Add
packages/contracts/src/fleet/fleet-machine-actions.types.ts(confirmation classes,FleetBlastRadius,FleetActionConfirmFields— the CONTRACTS §2.2 fieldsconfirm,confirmName,acknowledgeBlastRadius—FleetRootRequirement,FleetOperationRef) and export frompackages/contracts/src/fleet/index.ts.- Test:
packages/contracts/src/fleet/__tests__/fleet-machine-actions.spec.ts.
- Test:
- T-02. Add
fleet-services.types.tsandfleet-processes.types.tsunderpackages/contracts/src/fleet/(types,FLEET_SERVICE_ACTIONS, strict name patterns,FLEET_PROCESS_LIST_MAX = 300, signals).- Test:
fleet-services.spec.ts,fleet-processes.spec.ts— patterns reject;,|,$(, backticks, newlines, leading-.
- Test:
- T-03. Add the 19 §7 audit members to
FleetAuditAction/FLEET_AUDIT_ACTIONSinpackages/contracts/src/fleet/fleet-panic.types.tsandFLEET_SERVICES_CAPABILITY,FLEET_PROCESSES_CAPABILITYtofleet-jobs.types.ts.- Test: extend
fleet-panic.spec.tsand the jobs spec.
- Test: extend
P1.B — Shared node pieces
- T-04. Write
apps/node/src/core/link/modules/shared/root-runner.ts(runPossiblyAsRoot,sudo -nrefusal detection, Windows admin probe, exact-command rendering for both OS families, 60 s per-argv probe cache).- Test:
root-runner.spec.ts— refusal text variants →{ refused: 'root-required', command }; never passes-S/a password.
- Test:
- T-05. Write
apps/node/src/core/link/modules/shared/argv-safety.tsandblast-radius.ts(per-OS pattern table incl. the node's own unit/label/service name from install info; sentences per kind); add an eslintno-restricted-syntaxrule fordocker.sockstrings andchild_process.execunderapps/node/src/core/link/modules/**inapps/node/eslint.config.*.- Test:
argv-safety.spec.ts,blast-radius.spec.ts(NetworkManager, sshd, tailscaled, wg-quick@wg0, ever-works-node@ops, WindowsDnscache, launchdcom.openssh.sshd).
- Test:
P1.C — Services module
- T-06. Write
apps/node/src/core/link/modules/services/systemd-manager.ts(JSON list with text fallback,--user, enablement,MainPID, actions via root-runner).- Test:
systemd-manager.spec.tswith fixtures undermodules/services/__fixtures__/.
- Test:
- T-07 (parallel). Write
launchd-manager.ts(launchctl printparser, Homebrew JSON, at-boot semantics) andwindows-services.ts(CIM JSON, actions, StartupType).- Test:
launchd-manager.spec.ts,windows-services.spec.ts.
- Test:
- T-08. Write
apps/node/src/core/link/modules/services/index.ts(createServicesModule: detect manager,services.listwith ordering/CPU/mem via the processes module,services.action,failedCount()); register with one entry in FL-02's compile-time module arrayapps/node/src/core/link/modules/index.ts; advertiseservicesundermanagewhen a manager exists; feedfailedServicesinto the heartbeat health sample (FL-03 hook).- Test:
index.spec.ts— no tag without a manager; failed first; action result shapes; root refusal envelope.
- Test:
P1.D — Processes module
- T-09. Write
apps/node/src/core/link/modules/processes/process-list.ts(per-OS parsers, top 300, CPU% of one core, run time, command truncated 200 +redactSecrets) andself-protection.ts(own pid chain, daemon pid, pid 1).- Test:
process-list.spec.ts(Linux/macOS/Windows fixtures; redaction),self-protection.spec.ts.
- Test:
- T-10 (parallel). Write
gpu-stats.tsextendingapps/node/src/core/gpu-probe.tsvendors with utilisation/memory readers (nvidia-smi query, AMD sysfs, Applesystem_profilerwithutilizationPercent: null).- Test:
gpu-stats.spec.ts— Apple yields null utilisation, never 0.
- Test:
- T-11. Write
apps/node/src/core/link/modules/processes/index.ts(processes.list,processes.signalwith protection + root rule); register; advertiseprocessesundermanage.- Test:
index.spec.ts— protected pid →invalid; other user → root rule; TERM/KILL argv.
- Test:
P1.E — Platform guard and API
- T-12. Write
packages/agent/src/fleet/machine-actions/machine-action.guard.ts(consent → family via FL-01assertFamily→ kill switch → CONTRACTS §2.2 field check:confirm: true,confirmNameforconfirm-destructive,acknowledgeBlastRadius: trueforblast-radius→ typed refusal values) andmachine-action.audit.ts(row builder: name, target, requestedBy, class, outcome, blastRadius.kind, rootRequired). Export frompackages/agent/src/fleet/index.ts.- Test:
machine-actions/__tests__/machine-action.guard.spec.ts— every CONTRACTS §8 code; a missing field →confirmation_requiredwith the class (and the sentence forblast-radius); audit details passREDACTED_KEY_RE.
- Test:
- T-13. Write
packages/agent/src/fleet/services/fleet-services.service.tsandpackages/agent/src/fleet/processes/fleet-processes.service.ts(list via link, action via guard, audit, the node's root refusal surfaced asroot_required(409) with the command ashint).- Test:
services/__tests__/fleet-services.service.spec.ts,processes/__tests__/fleet-processes.service.spec.ts.
- Test:
- T-14. Write
apps/api/src/fleet/services/fleet-services.controller.ts+dto/,apps/api/src/fleet/processes/fleet-processes.controller.ts+dto/; register inapps/api/src/fleet/fleet.module.ts; guardsFleetEnabledGuard,FleetMachineFactsEnabledGuard; throttles per plan §5; Swagger.- Test:
fleet-services.controller.spec.ts,fleet-processes.controller.spec.ts;apps/api/test/fleet-machine-actions.e2e-spec.ts(fake link peer; list, action, root refusal).
- Test:
P1.F — Web
- T-15. Write shared components under
apps/web/src/components/fleet/shared/:ConfirmActionDialog.tsx(four classes; red "anyway" button; typed-name gate),RootRequiredDialog.tsx,machine-actions.shared.ts; addOperationCard.tsx+use-operation.tsonly if FL-02 did not ship them.- Test:
ConfirmActionDialog.unit.spec.tsx,machine-actions.shared.unit.spec.ts.
- Test:
- T-16. Write
apps/web/src/components/fleet/services/—ServicesTab.tsx,ServiceRow.tsx,services.shared.ts,services-tab.registry.ts(FL-01 entry{ id: 'services', labelKey, order, epic, flag, consent: 'manage', load }; palette "Services on" in FL-01's apps/web/src/components/command-palette/registry/sections.ts); BFF routesapps/web/src/app/api/fleet/nodes/[id]/services/route.tsand.../services/[name]/[action]/route.ts. Every state of spec §5.9; Log row action deep-links through FL-08'sopenLogsForSource.- Test:
services.shared.unit.spec.ts(ordering),ServicesTab.unit.spec.tsx(states, risk markers).
- Test:
- T-17 (parallel). Write
apps/web/src/components/fleet/processes/—ProcessesTab.tsx,GpuStrip.tsx,ProcessTable.tsx,SignalDialog.tsx,processes.shared.ts,processes-tab.registry.ts; BFF.../processes/route.ts,.../processes/[pid]/signal/route.ts.- Test:
processes.shared.unit.spec.ts,ProcessesTab.unit.spec.tsx(protected rows have no buttons; "still running — Kill?" after 5 s).
- Test:
P1.G — Tools, i18n, e2e
- T-18. Write
packages/agent/src/fleet/services/agent-fleet-services-tools.tsandpackages/agent/src/fleet/processes/agent-fleet-processes-tools.ts(annotations per plan §9; blast-radius / KILL below trust level →agent_action_proposalsrowfleet_machine_action(CONTRACTS R-33); approval re-runs with the §2.2 confirmation fields); concatenate besidebuildFleetTools; add MCP whitelist entries inapps/mcp/src/openapi-tools/whitelist.ts.- Test: both tool specs; whitelist spec.
- T-19. Add i18n groups
fleet.services.*,fleet.processes.*,fleet.actions.*,fleet.operations.*,fleet.computer.tabs.{services,processes}toapps/web/messages/en.json; writeapps/web/e2e/flow-fleet-services.spec.ts,flow-fleet-processes.spec.ts,flow-fleet-machine-actions-contract.spec.ts.- Test: the specs; dotted-key grep unchanged.
PHASE 2 — Docker containers and images
P2.A — Docker module
- T-20. Add
packages/contracts/src/fleet/fleet-docker.types.ts(containers, images, run request, compose shapes, name/ref patterns); export.- Test:
fleet-docker.spec.ts.
- Test:
- T-21. Write
apps/node/src/core/link/modules/docker/docker-cli.ts(argv builders, JSON-line parsers, PATH lookup only),containers.ts,images.ts.- Test:
docker-cli.spec.ts,containers.spec.ts,images.spec.ts(Engine + Desktop fixtures).
- Test:
- T-22. Write
run-request.ts(allow-listed flags; mounts under FL-08's allowed roots or named volumes; refuse privileged / host namespaces / cap-add / socket; env names on argv, values through the child's environment) andoperations.ts(FL-02 runner adapter, pull percent parser, last-20 records ≤ 1 MiB).- Test:
run-request.spec.ts(every refusal; argv never contains a value),operations.spec.ts.
- Test:
- T-23. Write
apps/node/src/core/link/modules/docker/index.ts(docker.containers|images,docker.container.actionstart/stop/restart/remove,docker.run,docker.image.pull|remove|prune); register whendockeris detected.- Test:
index.spec.ts— daemon down →operation_failedhint; remove in-use image →busy.
- Test:
P2.B — Docker API and service
- T-24. Write
packages/agent/src/fleet/docker/fleet-docker.service.tsandapps/api/src/fleet/docker/fleet-docker.controller.ts+dto/(routes per plan §5 — pull isPOST /nodes/:id/docker/images, removePOST …/images/:iid/remove, pruneDELETE …/images?unused=true, all §11.2 paths; containerremoverequiresconfirmName; operations return202 { operationId }); register infleet.module.ts.- Test:
fleet-docker.service.spec.ts(audit carriesenvVarNamesonly),fleet-docker.controller.spec.ts; extend the e2e spec with run/pull/prune lifecycle.
- Test:
P2.C — Docker web and Runs here
- T-25. Write
apps/web/src/components/fleet/docker/—DockerTab.tsx,ContainersList.tsx,ImagesList.tsx,RunContainerDialog.tsx(masked values, root-limited volume picker, command preview, disk-free line, cloud billing line via FL-17providerMeta),docker.shared.ts,docker-tab.registry.ts(tab throughregisterComputerTab, palette through FL-01'ssections.ts, Overview block throughregisterOverviewBlock);RunsHereBlock.tsx; BFF routes underapps/web/src/app/api/fleet/nodes/[id]/docker/**.- Test:
RunContainerDialog.unit.spec.tsx(refused options, preview line, no value in preview),docker.shared.unit.spec.ts,RunsHereBlock.unit.spec.tsx.
- Test:
P2.D — Docker tools, i18n, e2e
- T-26. Write
packages/agent/src/fleet/docker/agent-fleet-docker-tools.ts(fleet_docker_containers_list,fleet_docker_container_action,fleet_docker_run,fleet_docker_images_list,fleet_docker_image_action; annotations per plan §9; Approvals forremove/prunebelow trust); MCP whitelist entries;fleet.docker.*i18n;apps/web/e2e/flow-fleet-docker.spec.ts(P2 part).- Test: tool spec; whitelist spec; e2e.
PHASE 3 — Compose and shell inside
P3.A — Compose module and API
- T-27. Write
apps/node/src/core/link/modules/docker/compose.ts(compose ls,config,psmerge intoFleetComposeDescription;filesMissing; actions up/down/restart/pull as operations, optionalservice;downnever passes-v) and wirecompose.list|describe|actionindocker/index.ts.- Test:
compose.spec.ts— depends_on conditions, missing files → Down only, argv never has-v.
- Test:
- T-28. Extend
fleet-docker.service.ts/fleet-docker.controller.tswith the compose routes (plan §5:GET /nodes/:id/docker/compose[?project=],POST …/compose/:project/:action) andexec({ sessionId }via FL-04FleetSessionService.startOwner).- Test: service/controller specs; e2e compose lifecycle with the fake peer.
P3.B — Compose web
- T-29. Write
ComposeList.tsx,ComposeProject.tsx,ComposePicture.tsx(SVG),compose-layout.ts(layered DAG, deterministic),ComposeServiceTable.tsxunderapps/web/src/components/fleet/docker/; Logs view calls FL-08'sopenMergedLogs.- Test:
compose-layout.unit.spec.ts(layering, cycle tolerance, stable order),ComposePicture.unit.spec.tsx(amber arrow whileservice_healthyunmet; red when down).
- Test:
P3.C — Shell inside
- T-30. Write
apps/node/src/core/link/modules/docker/exec-session.ts(shell detection inside the image; daemon start request['docker','exec','-it',id,shell], kind owner, titleShell · inside <name>); add Shell inside ▾ toContainersList.tsx, the picture and the service table, opening the Session via FL-04's terminal route; auditnode.docker.container.exec+node.session.start.- Test:
exec-session.spec.ts; e2e: shell appears on the Sessions tab and ends with the Session.
- Test:
- T-31. Add
fleet_docker_compose_list,fleet_docker_compose_describe,fleet_docker_compose_action,fleet_docker_exectoagent-fleet-docker-tools.tsand the MCP whitelist;fleet.docker.compose.*i18n; finishflow-fleet-docker.spec.ts(P3 part).- Test: tool spec; whitelist spec; e2e.
Docs and rollout
- T-32. Write
docs/features/fleet-services-docker.md(the three tabs, the root rule and how to pre-authorisesudo -nfor the node's user, blast-radius sentences, Docker CLI-only policy and refused run options, Compose views, shell inside, Agent tools); add after'features/fleet'inapps/docs/sidebarsPlatform.ts(:183, after FL-08's entry) and a row indocs/features/index.md.- Test:
pnpm --filter ever-works-docs build— no broken links.
- Test:
- T-33. Update
../TRACKER.mdFL-09 row and theStatusfields of the three documents; add the CONTRACTS §10 rows raised in spec §8b in the same PR.- Test: docs build clean; the banned-terms grep over this folder prints nothing.
- T-34. Run
pnpm format && pnpm lint && pnpm test && pnpm build; run the banned-terms grep over this folder — it must print nothing.- Test: the four commands exit 0; the grep prints nothing.
Definition of done
- All checkboxes ticked; ACC-FL09-01…23 green on
developbehindFLEET_MACHINE_FACTS_ENABLED. - Static rule proves no code under
apps/node/src/core/link/modules/names the Docker socket or shells out with a string; the e2e proves env values never appear in audit, operation output or tool results. pnpm format:check,pnpm lint,pnpm test,pnpm buildgreen; docs build clean.- Constitution gates in
spec.md§10 confirmed; TRACKER row updated.