> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Service standards

> The eight things a complete service must have, and what is checked today.

A service is a `tier:service` [member](/standards/members) at `services/<name>`, named `@figentra/service-<name>`, with its entry at `src/main.ts`. Services are independent workspace members. Service source holds no Encore files: Encore is the **generated** runtime target, emitted by `stackra gen` into `.stackra/encore/` ([0091](/adr/0091-generated-encore-app-in-stackra-folder), [0124](/adr/0124-encore-is-the-generated-runtime-target)). The generator is not built yet, so the Encore-specific rules below ("when Encore lands") describe the target, not today's code.

## Layout

`pnpm new service <name>` renders it ([0126](/adr/0126-member-shapes-and-scaffold-templates)):

```text theme={"system"}
services/<name>/
  package.json        "stackra": { "module": "./src/<name>.module.ts" } (0090), ./contracts export
  turbo.jsonc  tsconfig.json  tsdown.config.ts  vitest.config.ts
  README.md  AGENTS.md (with ## PII)  CHANGELOG.md  LICENSE
  config/<name>.config.ts         deploy-time defaults (CF1)
  src/
    main.ts                       entry: bootstraps the root module
    <name>.module.ts              root module; feature modules join its imports
    modules/<feature>/            kind folders: models/ actions/ queries/ services/ events/ listeners/
                                  workflows/ policies/ permissions/ settings/ contracts/ resources/
    contracts/index.ts            types other members may import
    __tests__/<name>.module.test.ts
```

There are no `api.ts`, `db.ts` or `encore.service.ts` files: endpoints, topics, crons and databases are declared with
decorators in the modules and `stackra gen` emits the Encore primitives into `.stackra/encore/`. A new service is a
skeleton with no feature module; `src/modules/README.md` lists the kind folders.

## The eight non-negotiables

**1. Placement, and one file per primitive**

The placement test passes: a second product with none of this product's domain would still need
it verbatim, so it belongs in a service named for the capability, not for the product (see
[Boundaries](/constitution/boundaries)). Primitives follow the file layout above.

**2. Two-layer tenant isolation, always**

Every query runs inside the tenancy helper, and every tenant-owned table has `tenant_id uuid NOT
        NULL` with row-level security enabled **and** forced. Tenant, scope and actor come only from the
authenticated principal, never from request input. Full rule: [Tenancy](/constitution/tenancy).
**In force from the first tenant-owned table.**

**3. Event-first**

Cross-service communication is a versioned event unless the caller needs an authoritative
decision now, and then only through a typed client on a short, closed list of allowed
synchronous calls. A service imports another service's `Topic` and nothing else from it. No
distributed transactions: order steps so a partial failure is safe and reconcilable. See [HTTP
and events](/constitution/http-and-events).

**4. One gateway**

When Encore lands, the single `encore.app` is the only gateway and CORS configuration; a service
never sets CORS, and `auth: true` is the default on every endpoint. Until then a service exposes
no public surface.

**5. Idempotency on every write**

Writes carry an `Idempotency-Key` header and the service dedupes for 24 hours per tenant.
Financial writes also run in one transaction and emit an audit event. See [HTTP and
events](/constitution/http-and-events).

**6. Required files, present and current**

Everything in [Member standards](/standards/members), plus `isolation.test.ts` once a scoped
table exists. `AGENTS.md` carries a PII table even when it says none.

**7. Secrets**

Application secrets are declared by name (`secret("NAME")`) and set by a human; never in a file,
test, log or chat. Tests need zero secrets. See [Secrets](/constitution/secrets).

**8. Definition of done**

Code, `pnpm verify` green, the service's own tests green, docs current, reviewer sign-off. A
service is not "done" because it typechecks: `pnpm verify` never starts a service (see
[Testing](/standards/testing#what-a-green-run-proves)).

## Service boundary

* A service owns its entry, its database and migrations, its topics, its public API and its tests.
* No service imports another service's internals. Cross-service calls go through the generated typed client; shared types go through the producer's `./contracts` entry ([ADR 0117](/adr/0117-contracts-placement-and-decorator-ownership)).
* Reading another service's database is allowed only read-only, only where an ADR names the pair.
* Cross-service reads: call the owner's API, or subscribe and keep a small local read model. Cross-service writes: call the owner's API inside the caller's flow, or publish a versioned event and let the owner react.
* Service calls are written inside a service directory so the framework can resolve them statically; shared library code takes callbacks instead.
* Data ownership and the ORM: [ADR 0014](/adr/0014-data-ownership-and-orm).

## Adding a service

1. `pnpm new service <name> --description "<one sentence>"` (or `--repo <path>` for another monorepo).
2. Fill `README.md`, `AGENTS.md` and its `## PII` table.
3. Add a feature module under `src/modules/<feature>/` and import it from the root module, kind folders only as files appear.
4. Run `pnpm install` then `pnpm verify`.

## What enforces this

| Rule | Check | Status |
| - | - | - |
| Member files, PII heading, `tier:service` tag, catalog versions | `validate-repository.mjs` | Yes |
| A package or app never imports a service | `turbo boundaries`, oxlint `boundaries.jsonc` | Yes |
| A service's `internal/`, `entities/` and `db` are private | oxlint `boundaries.jsonc` (`services/**` rule) | Yes |
| `src/main.ts`, `stackra.module` names an existing root module, no Encore file | `validate-repository.mjs` (`checkService`) | Yes |
| One `api()` per operation, `auth: true` default | planned: lint rule when Encore lands | No |
| Generated client has not drifted | planned: `gen:client:check` in `pnpm verify` | No |
| Tenant isolation (RLS audit, `isolation.test.ts`) | planned: from the first tenant-owned table | No |
| Application secrets declared, never valued | [Secrets](/constitution/secrets) checks (partly planned) | Partial |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.