arch-scaffold-worker · git:20261009.511900f · 2026-10-09 · sha256 3f0bbe7d89277bc9
arch-scaffold-worker git:20261009.511900fA
Immutable. This exact content never changes and is served at /api/v1/blob/3f0bbe7d89277bc9.
--- name: arch-scaffold-worker description: "Add a kind of background work: the kind, its payload and permission, its handler in the maintenance worker or in a worker of its own, and the tests, in the shape of the scaffold's work queue and worker. Python." allowed-tools: Read, Grep, Glob, Write, Edit, Bash(make check), Bash(make infra-up), Bash(make migrate), Bash(make migrate-check), Bash(docker info:*), Bash(uv run:*), Bash(uv sync:*), Bash(git status:*) --- # arch-scaffold-worker A path that starts with `../` is read from this skill's folder as `realpath` resolves it. Conventions: `../_shared/scaffold-conventions.md`. Sections of `../../architecture.md`: The Business Layer (Operations Without a Principal), The Storage Layer (Database Roles), Infrastructure (Topics, Idempotency), The Network Layer (Long-Running Orchestrations), Worker Roles (The Work Queue, Shape of a Worker, Shutdown, Maintenance Without a Scheduler), Deployment (Infrastructure as Code). ## Input `<worker-name> <WORK_KIND> [--lane <name>] [--container]` Example: `maintenance NOTIFY_SHIPMENT`. Both are required; ask when missing. `<Kind>` is the kind in CamelCase. When `<worker-name>` is a worker the tree has (`maintenance` in a copy), the kind joins it. Otherwise the skill creates the worker first. A kind that needs its own capacity (`--lane <name>`) is first its lane in `WORK_LANES`, which the relay lands its items on, and a replica of the maintenance worker on that lane, which takes it from its deployment spelled as `WORK_LANES` spells it (`<NAME>_WORKER_LANE`, or `serve --lane`): one line of code and a process in each place the worker runs. Create a worker when the kind needs code or a dependency the maintenance worker should not carry. ## Created | File | Holds | |------|-------| | `workers/<worker-name>/src/<name>/workers/<worker>/<kind>.py` | `<Kind>HandlerImpl(WorkHandlerInterface)` with its `REQUIRES`, shape `handler.py` and `accounts.py` of `workers/maintenance/` | | `workers/<worker-name>/tests/test_<kind>.py` | the handler over the memory container, run twice with the same item | | `workers/<worker-name>/` (a new worker) | the distribution, shape `workers/maintenance/`: settings, container, loop, entry, main, health, and their tests, without the sweep and the delivery consumer, which stay in the maintenance worker | | `deployment/docker/<worker-name>.Dockerfile` (a new worker) | the image, shape `maintenance.Dockerfile` | ## Changed | File | Change | |------|--------| | `om/src/<name>/om/work/types/work_item.py` | the kind in `WorkKind`, its payload in `WORK_PAYLOADS`, its permission in `WORK_ENQUEUE_PERMISSIONS`, and with `--lane` its lane in `WORK_LANES` | | `om/src/<name>/om/work/README.md` | the kind, in the product's language | | the worker's `main.py` | the handler in `handlers` of `build_loop` | | the producing manager's impl | the write that starts the work lands a row of kind `work_row_kind(WorkKind.<KIND>)` beside its own | | `pyproject.toml` (root), `scripts/dev.sh`, `.env.example` (a new worker) | the member, the process started, and every field of its settings | | `deployment/local/docker-compose.full.yml` (a new worker, with `--container`) | the worker as a container | | `deployment/terraform/modules/environment/main.tf`, `modules/account/variables.tf`, both deploy workflows (a new worker) | an instance of the service module beside `module "maintenance"`, with no load balancer route; its image in `images`; its image built once by staging and promoted by digest | | `scripts/dev.sh`, `deployment/local/docker-compose.full.yml`, `deployment/terraform/modules/environment/main.tf` (`--lane` on a worker the tree has) | a second process of the worker on the lane: in `dev.sh`, `serve --lane <lane>` with a metrics port of its own (`<NAME>_METRICS_PORT`); a second service of its image, and a second instance of its service module beside its own, each with `<NAME>_WORKER_LANE` set to the lane | | `infra/tests/test_env_files.py` (a process added to `scripts/dev.sh`) | the count of processes the script starts, in `test_dev_script_takes_the_exported_value_over_dotenv` | ## Procedure 1. Add the kind, its payload shape on the `Platform` base, and the permission a producer needs to ask for it. The handler's `REQUIRES` names the permissions its calls take, and the test that holds the two to each other fails until they agree: nobody reaches through the queue what they could not do directly. 2. The handler runs under the context the claim returned, and never builds one. Handling an item twice changes nothing. A write to the record the item advances is a compare-and-set on the record's version, since the fences guard the queue row and not the record. An effect the handler must make happen rides an outbox row or a work item, never a topic alone. 3. A long-running kind is an `OrchestrationKind` whose step is mapped in `build_loop`, not a handler that loops; it parks or fails as Long-Running Orchestrations states. A lane is a work kind's, and every orchestration kind's steps ride `WorkKind.ORCHESTRATION`, so `--lane` gives no one orchestration kind a lane of its own. A long-running kind that needs one is a long-held work kind whose lease its worker renews; one that must stay an orchestration runs on the orchestration lane, and the output says so. 4. A producer never enqueues from a manager. Its write carries the work row in the same storage call, and the relay enqueues the item under the row's id. 5. With `--lane`, the kind's lane is a line in `WORK_LANES`, never an edit of the relay. A replica of a worker the tree has takes the lane from its deployment, spelled as `WORK_LANES` spells it. A worker of the kind's own reads its lane through `relayed_lane(WorkKind.<KIND>)`, never a copy of the name, so the relay and the worker cannot disagree. Its lane setting is a field of the worker's own name (`<worker>_lane`, so `<NAME>_<WORKER>_LANE`), never `worker_lane`: `.env.example` sets `<NAME>_WORKER_LANE=default`, and `scripts/dev.sh` exports it to every process it starts. The field defaults to `None`, its line in `.env.example` stays commented, its settings test lists it as one the cloud leaves at its default, and the loop takes `relayed_lane(WorkKind.<KIND>)` while it is unset. 6. A new worker sets its capacity against the pool it opens for the roles it touches (`<NAME>_DATABASE_POOL_SIZE`), never apart from it. Add its member and run `uv sync` before the fast gate. ## Output As `../_shared/scaffold-conventions.md` states.