arch-scaffold-new · git:20260922.a3c1e91 · 2026-09-22 · sha256 fa8a9bb70934dbf6
arch-scaffold-new git:20260922.a3c1e91A
Immutable. This exact content is served forever at /api/v1/blob/fa8a9bb70934dbf6.
---
name: arch-scaffold-new
description: "Bootstrap a whole new system in the guideline's shape into an empty folder: the monorepo skeleton, the first API, a worker, a portal, deployment, CI, then the first namespace and entity."
allowed-tools: Read, Grep, Glob, Write, Edit, Agent, Bash(make setup), Bash(make check), Bash(make infra-up), Bash(make migrate), Bash(make migrate-check), Bash(make seed), Bash(make test-integration), Bash(make openapi), Bash(make devx-up), Bash(make test-telemetry), Bash(make traffic PROFILE=light DURATION=30), Bash(uv sync:*), Bash(uv run:*), Bash(pnpm install:*), Bash(pnpm run:*), Bash(pnpm --filter:*), Bash(git init:*), Bash(git status:*), Bash(git rev-parse:*), Bash(python3:*), Bash(git diff:*), Bash(git log:*), Bash(git merge-base:*), Bash(git symbolic-ref:*)
---
# arch-scaffold-new
Conventions: `${CLAUDE_SKILL_DIR}/../_shared/scaffold-conventions.md`.
Sections of `${CLAUDE_SKILL_DIR}/../../architecture.md`: Naming
Entities, Namespaces as Swimlanes, OpContext (Stages, Scopes, The
Operator Context), The Business Layer (Operations Without a Principal,
Shape of an Operation), The Storage
Layer (Namespace Shape, Storage Root, Defining ORM Classes,
Translation, A Storage Impl, Database Roles, The Second Fence,
Migrations), Infrastructure (InfraInterface Root, Cache, Buckets,
Topics, Queues, Secrets, Idempotency), The Network Layer
(The Gateway; Auth: the Gateway Verifies, the Tenancy Domain Owns;
Realtime at the Edge), Deployment (Cloud: AWS, Infrastructure as Code,
Local: Docker Compose, Twins for External Services, What a Process
Refuses), Operations (Operator Credentials, Operational Skills,
Dashboards and Alarms as Code, Traffic and Stress, The Telemetry Round
Trip), Monorepo Folder Structure (Layout Conventions),
Documentation as Code (A README at Every Level), Telemetry,
Cross-Cutting Conventions
(Exceptions, Configuration, Records of Decisions, Tests), Technology
Choices and How to Override Them (Versions, Overriding a Choice).
## Input
`<target-dir> <root-package> [--first <namespace> <Entity> [field:type ...]] [--codeowners <owner,...>] [--no-portal] [--no-worker]`
Example: `./acme acme --first inventory Warehouse address:str`. Both
positional arguments are required; ask for them when missing.
`<target-dir>` must not exist, or must be empty, or be a fresh
repository holding nothing but `.git`, `README.md`, `LICENSE`, and
`.gitignore` (the shape a hosting service creates); refuse otherwise.
In the fresh-repository case `README.md` and `.gitignore` are replaced,
`LICENSE` is kept, and the `git init` of step 1 is skipped. Those two
replacements are the one exception to the collision rule of the
conventions; any other path that exists is a collision. Refuse when a `.git`
directory exists in a parent of `<target-dir>` (`git rev-parse
--show-toplevel` from it names one), because `git init` never runs
inside an existing repository.
`<root-package>` must not shadow a standard-library module. In this
skill `<root>` is `<root-package>`, and `<root-slug>` its kebab-case
slug, as the conventions' Naming derives it (`acme_corp` gives
`acme-corp`). `--codeowners` names the owners `.github/CODEOWNERS`
lists, the repository's owner by default (the account or organization
in the `origin` URL of `.git/config`; ask when there is none).
## Created
Everything the references below list is under `<target-dir>/`. Those
lists are long, so each one lives beside this file and is read by the
step that names it, when that step runs and not before.
| Reference | Holds | Read by |
|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| `${CLAUDE_SKILL_DIR}/references/skeleton.md` | the workspace and tool config, the `Makefile`, the README, the docs, the two ADRs and the runbooks, the local compose stack, the Terraform modules and roots with the environments' file, the two cloud scripts, the six workflows, and the nine operational skills | step 1 |
| `${CLAUDE_SKILL_DIR}/references/object-model.md` | the OM distribution under `om/`: the base module, the context stages and scopes, the exceptions, the storage layer with its roles, tables, translation, impls, and migrations, and the `tenancy`, `events`, `audit`, `outbox`, and `idempotency` namespaces with their tests | step 1 |
| `${CLAUDE_SKILL_DIR}/references/infrastructure.md` | the infra distribution under `infra/`: the cache, buckets, topics, queues, and secrets capabilities, observability and the trust store, and the configured and local roots | step 1 |
| `${CLAUDE_SKILL_DIR}/references/ops-package.md` | `clients/python/`, the Python client generated from the API's document, and `ops/`, the `<root>-ops` member with the traffic generator, the stress runner, the signals interface, and the telemetry round trip | step 3 |
| `${CLAUDE_SKILL_DIR}/references/api-sweep.md` | the API process's own sweep, `services/api/.../sweep.py`, the loop of `arch-scaffold-worker` without the queue, written only when the tree has no worker | step 4, with `--no-worker` |
A reference file is detail. These are the lines a run must never miss,
so they stay here:
- The guideline release is pinned once. `specs/architecture.md` names
the tag the conventions found before writing, and the `Makefile`'s
`arch-check` runs that same tag.
- Three logins reach the database, as the conventions state. The local
compose file's init script creates them under the names every
environment uses, `<root>_migration`, `<root>_runtime`, and
`<root>_system`, since a policy spells the system login's name.
- Every table declares its role and its tenancy scope, and the
migration that creates the table creates its policy, as the
conventions state. A chain's first migration also creates its role's
schema and grants the runtime and system logins on it.
- No secret value in Terraform state or a plan. Every password comes
from an ephemeral generator through a write-only attribute, and no
URL secret is computed as an output.
- Every health check, the target group's and each task definition's
own, is on `/healthz`, never `/readyz`.
- A durable effect never rides a topic alone. What must happen after a
write rides an outbox row or a work item, and a topic, at most once,
carries hints only (`ENTITY_CHANGED`, `WORK_AVAILABLE`), so a lost
message delays an effect and never drops it.
- An event, an audit entry's payload of ids, an outbox row, and a
socket frame carry ids only, never a personal field's value.
- Every settings knob is documented. A unit test holds every field of
`StorageSettings`, of `InfraSettings`, and of each process's own
settings to `.env.example` under its prefix.
## Changed
| File | Change |
|------|--------|
| (none) | The tree is new; every later step appends to the files the references name. |
## Procedure
1. `git init` in `<target-dir>`, nothing staged (skipped when the
target was a fresh repository). It comes first so that every step
after it, and every skill this one follows, lists its files from
`git status`. Then write the skeleton, reading
`${CLAUDE_SKILL_DIR}/references/skeleton.md` before it (leaving
`clients/python/` and `ops/` with its `README.md` to step 3); then
the OM distribution, reading
`${CLAUDE_SKILL_DIR}/references/object-model.md` before it; then the
infra distribution, reading
`${CLAUDE_SKILL_DIR}/references/infrastructure.md` before it; then
run `make setup`. The fast gate runs from
step 2 on. The nine operational skills are part of the skeleton:
copy each template under `${CLAUDE_SKILL_DIR}/../_shared/ops-skills/` to
`.claude/skills/<name>/SKILL.md` with `acme` substituted, as the
skeleton reference states, and change nothing else in them.
2. Read `${CLAUDE_SKILL_DIR}/../arch-scaffold-service/SKILL.md` and
follow its Created, Changed, and Procedure with these arguments:
`api --realtime --container` (omit `--realtime` with `--no-portal`).
The operator plane's routes arrive with it.
3. Read `${CLAUDE_SKILL_DIR}/references/ops-package.md`, then
`make openapi` and write `clients/python/` generated from the
document it emitted, whether or not `--no-portal`; then write
`ops/` and `ops/README.md` over that client, add both members to
the workspace, and run `uv sync`. The ops package rides the client
and the operator plane, so it is written after both exist.
4. Unless `--no-worker`, read
`${CLAUDE_SKILL_DIR}/../arch-scaffold-worker/SKILL.md` and follow
it with `maintenance NOOP --container`: a worker whose only work is
the maintenance sweep, ready for real kinds. With `--no-worker`,
the sweep moves into the API process's lifespan, so outbox rows a
crash left behind are still relayed: read
`${CLAUDE_SKILL_DIR}/references/api-sweep.md` and write
`services/api/src/<root>/services/api/sweep.py` from it. Whichever
path ran, the sweep sets the gauge under the one name the
outbox-lag alarm and the copied `ops-investigate` skill read,
`<root>_outbox_lag_seconds`, so a tree with no worker is watched
like a tree with one. Every API replica runs the sweep, which is
safe because every step is idempotent and each purge is bounded by
a batch size.
5. Unless `--no-portal`, read
`${CLAUDE_SKILL_DIR}/../arch-scaffold-app/SKILL.md` and follow it
with `portal --kind portal`, including its Terraform and deploy
rows: the portal's bucket and distribution exist in every
environment before this step is done. Its Python client row is
skipped, since step 3 wrote the client.
6. With `--first`, read
`${CLAUDE_SKILL_DIR}/../arch-scaffold-namespace/SKILL.md` and follow
it with `<namespace> <Entity> <field:type ...>`.
7. `make check` and `make openapi`, so the portal's generated types
and the Python client carry the routes of step 6; then, when Docker
is available, `make infra-up`, `make migrate`, `make migrate-check`,
`make seed` twice (the second run changes nothing, and leaves
`local.env` as the first wrote it), and `make test-integration`,
only against the
compose stack of step 1: refuse when any database URL `StorageSettings`
resolves (the three shared URLs and every per-role URL, from the
environment, `.env`, or the settings default) is not a local
address.
8. When Docker is available, run the negative control of
Cross-Cutting Conventions (Tests) once. Take the tenant predicate
out of one query of a storage impl over Postgres (the first
entity's list with `--first`, else a tenancy list). Run
`make test-integration` with the table's policy in place: it stays
green, the second fence holding. Turn the policy off for that
table (`ALTER TABLE ... NO FORCE ROW LEVEL SECURITY` and `DISABLE
ROW LEVEL SECURITY`, through `uv run` over the local migration
login's URL, since only the owner alters a table), and run `make test-integration` again: it fails, and the
failures name the cross-tenant case of that method beside the
policy check. Put the predicate back, turn the policy on again the
same way (`ENABLE` and `FORCE ROW LEVEL SECURITY`), and run
`make test-integration` green. Record both runs in
`docs/runbooks/tenant-isolation.md`: the query, the table, and
what the suite reported each time. A run two that stays green is a
defect of the suite: name it in the output and stop.
9. When Docker is available, `make devx-up`, then
`make test-telemetry`: the round trip starts the API as a real
process, drives one session, and reads the counter, the trace, the
error event, and the log line back by request id through the
`devx` twins. Then `make traffic PROFILE=light DURATION=30`, the
thirty-second light run, the same one CI's integration job runs.
Both are a wiring check of the edge, the
client, the generator, and the signals, and never a stress test;
a stress test has a scenario and a target, and is the platform
developer's to run.
10. Before the review, sweep the tree for the four misses a fresh
scaffold makes most, and fix each: a setting the Terraform root
does not pass to the service, a mutating manager operation whose
first line is not `ctx.require(...)` or `octx.require(...)`,
leaving out the operations the conventions exempt (the request
stage, the identity stage, and the outbox handoff), a socket route mounted
outside the gateway, a route that writes a durable row (201 or 202)
without the `Idempotency-Key` dependency. Then read
`${CLAUDE_SKILL_DIR}/../arch-review-full/SKILL.md`
and run it over the whole tree; the checker run and the git reads
it takes are in this skill's tools for that step. Close every high
finding and rerun `make check`; list the rest in the output for the
person. A high
finding on a fresh tree is a defect of this skill: name it in the
output so it can be closed at the source.
Stop at the first step whose gate fails and report where it stopped.
## Output
As `${CLAUDE_SKILL_DIR}/../_shared/scaffold-conventions.md` states,
plus one line: the tree is uncommitted, and the first commit is the
user's.