---
id: w1/m2.5/t003
title: Extract internal/apps — write side + single-writer wiring
worker: worker1
status: done
estimate: 25m
depends_on: [w1/m2.5/t002]
---

## Objective

Add the App-lifecycle **write** verbs into the `internal/apps` package built in t002: `Restart`, `Suspend`, `Resume`, the `setSuspended` single-writer-of-intent logic (`Store.SetAppSuspended` through the narrow `IntentStore`), and the REST/GraphQL/MCP mutation fragments — plus the intent tests.

## Context

- Source: `internal/api/core.go` (`Restart`, `Suspend`, `Resume`, `setSuspended`) and the mutation slices of `rest.go` / `graphql.go` / `mcp.go`; tests in `intent_test.go`.
- Single-writer rule (m2): for store-managed Apps the row is written first (the projector owns `spec.suspended` and would revert a bare CR patch); the CR patch is the fast-converge path. Keep the narrow `IntentStore` seam — the apps service depends on that one method, not the whole store.
- Reuses `fetch`/`patch` from t002. Write fragments: REST `POST /v1/services/{id}/{suspend|resume|restart}`, GraphQL `suspendService`/`resumeService`/`restartServer`, MCP `restart_service`/`suspend_service`/`resume_service` (the bex-only extensions Render's MCP omits, following Render's naming).

## Steps

1. Add `Restart`/`Suspend`/`Resume` + `setSuspended` to `internal/apps/service.go`, each starting with the `authorize` gate (`relCanOperate`).
2. Wire `IntentStore` and depend on it (not the whole `internal/store`).
3. Add the mutation fragments to the feature's `rest.go`/`graphql.go`/`mcp.go`.
4. Move `intent_test.go`; keep the single-writer + restart-stamp assertions.

## Files

- `lego/backend/internal/apps/{service.go,rest.go,graphql.go,mcp.go,*_test.go}`
- `lego/backend/internal/api/{core.go,rest.go,graphql.go,mcp.go}` (remove moved write code)

## Acceptance criteria

- [ ] `go build ./... && go test ./...` green.
- [ ] Restart/Suspend/Resume reachable and identical over REST, GraphQL, and MCP.
- [ ] Suspend/Resume write the store row first for store-managed Apps (single-writer test passes); Restart stamps `spec.restartedAt` with no row write.

## Out of scope

- Logs/metrics/apikeys/postgres (t004–t006).
