Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/02-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ the per-request loaders; services never see GraphQL.
flowchart TB
subgraph HTTP["HTTP · src/server.ts"]
F["Fastify<br/>POST /graphql · GET /health"]
Y["GraphQL Yoga<br/>CORS · masked errors · depth limit 12 · introspection switch"]
Y["GraphQL Yoga<br/>CORS · masked errors · depth and cost limits · introspection switch"]
end
subgraph GQL["GraphQL · src/graphql + src/schema"]
SDL["SDL per module<br/>src/schema/*.graphql.ts"]
Expand Down Expand Up @@ -75,7 +75,7 @@ src/
errors.ts ERROR_CODES, toGraphQLError, withErrorCodes
scalars.ts DateTime, Day, JSON
loaders.ts per-request BatchLoader per entity (no N+1)
limits.ts depthLimit
limits.ts depthLimit, costLimit
device.ts DeviceInput (collected by security-kit in the client) → auth-kit DeviceSignals
jobs/
types.ts QUEUES, job payloads, JobInfo, JobQueue, JobFailure
Expand Down Expand Up @@ -110,7 +110,7 @@ sequenceDiagram
participant DB as Database
Cl->>Fa: POST /graphql (Authorization: Bearer access JWT)
Fa->>Yo: handleNodeRequestAndResponse
Yo->>Yo: parse, validate (depth ≤ 12, introspection rule)
Yo->>Yo: parse, validate (depth ≤ 12, cost limits, introspection rule)
Yo->>Cx: build context
Cx->>Au: context(t) → verifies the JWT, checks the session, re-reads the user
Au-->>Cx: viewer, can(), branchesFor()
Expand Down Expand Up @@ -181,4 +181,4 @@ builds it from the environment. `close()` closes what the container opened.
`branchScope`, which narrows to the branches where the user holds the permission.
3. **Transactions.** Multi-record writes run in `repos.transaction()` inside the kits.
4. **No N+1.** Nested fields (`Booking.patient`, `Invoice.branch`, …) go through per-request loaders.
5. **Limits.** Query depth ≤ 12, list `limit` ≤ 200, request body ≤ 2 MB by default.
5. **Limits.** Query depth ≤ 12; per operation ≤ 500 fields (aliases and fragment spreads count), ≤ 20 top-level fields and ≤ 3 document / export fields (`LIMITS` in `src/server.ts`; more documents go through `startDocumentJob`); list `limit` ≤ 200, request body ≤ 2 MB by default.
2 changes: 1 addition & 1 deletion docs/05-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Every error in a response carries `extensions.code`. Clients switch on the code,
| `RATE_LIMITED` | 429 | Too many attempts; `extensions.retryAfterMs` says how long to wait | Wait, then retry |
| `WEAK_PASSWORD` | 400 | The new password fails the policy | Show the message |
| `INVALID_TOKEN` | 400 | A reset token is unknown, used or expired | Request a new link |
| `GRAPHQL_VALIDATION_FAILED` | 400 | The document is invalid (unknown field, depth > 12, introspection off) | Fix the query |
| `GRAPHQL_VALIDATION_FAILED` | 400 | The document is invalid (unknown field, depth > 12, more than 500 fields / 20 top-level fields / 3 documents, introspection off; no "Did you mean" hints when introspection is off) | Fix the query |
| `INTERNAL_SERVER_ERROR` | 500 | Anything else. The message is masked as "Unexpected error" | Retry later; check server logs |

## How errors are made
Expand Down
9 changes: 8 additions & 1 deletion docs/06-auth-and-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ the user cannot do.
| `accountant` | Payments, invoice cancellation, all `accounts:*` (books, GST, finance), audit |
| `doctor` | Reads patients and reports |

Organisations can add their own roles or override these with `upsertRole`.
Organisations can add their own roles with `upsertRole` (system roles cannot be changed).

**No escalation.** Granting a role (`createUser`, `setUserRoles`), managing a user (`setUserRoles`,
`setUserPassword`, `disableUser`, `enableUser`) and defining a role (`upsertRole`) need, besides the
permission itself, **every permission of the roles involved**, held in the same branch or
organisation-wide (an organisation-wide grant needs organisation-wide roles). A custom "user manager"
role with `user:create` can create front-desk users only if it holds the front desk's permissions, and
can never mint an owner, promote itself, or set the owner's password: `FORBIDDEN`.

### How a resolver checks

Expand Down
4 changes: 2 additions & 2 deletions docs/11-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ flowchart LR

| Area | What the suite proves |
|---|---|
| Server | `/health` and `{ health }`, introspection off when `INTROSPECTION=0`, depth limit 12 |
| Server | `/health` and `{ health }`, introspection off when `INTROSPECTION=0` (and no field suggestions), depth limit 12, field / top-level / document limits |
| PgBouncer | The whole PostgreSQL suite again through PgBouncer in transaction mode (`pgbouncer` on the PATH or `PGBOUNCER_BIN`; skipped with a note otherwise) |
| Replicas | Two API servers sharing PgBouncer and Redis: a session, a refresh and a logout cross replicas; 16 concurrent invoices with unique numbers over a 3-connection server pool; shared login limits; draining and shutdown |
| Auth | no token → `UNAUTHENTICATED`, wrong password → `INVALID_CREDENTIALS`, login, viewer (null without a token), weak password, reset link through the mailer (unknown emails not revealed), reset ends old sessions, disable signs out at once, `mySessions`, logout |
| Permissions | front desk cannot read the books; registers in branch A, not B; lists show only the viewer's branches; discounts need `invoice:discount`; nested vouchers hidden without `accounts:read` |
| Permissions | front desk cannot read the books; registers in branch A, not B; lists show only the viewer's branches; discounts need `invoice:discount`; nested vouchers hidden without `accounts:read`; a custom user-manager role cannot mint an owner, promote itself or set the owner's password |
| Tenancy | another organisation cannot read the invoice, the patient, or print it; cannot post into its ledgers |
| Catalog | search and price quote without sign-in |
| Lab | patient → booking by catalog code → collect → receive → results with unit, range and flag from the catalog → unverified report does not print → verify, release → booking completed |
Expand Down
Loading
Loading