> For the complete documentation index, see [llms.txt](https://help.resouris.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.resouris.com/reference/architecture.md).

# Architecture

**Audience:** technical readers (IT leads, developers, security reviewers). If you are non-technical, [Overview](/readme.md) and [Core concepts](/part-2-running-resouris/deployment-modes.md) will serve you better.

This page is a high-level snapshot. The authoritative architecture lives in the corpus at `v1.1/docs/architecture/`. External-facing wire contracts are pinned there and cross-checked by CI.

## The four layers

ReSouris follows a strict four-layer separation:

```
┌────────────────────────────────────────────────────────┐
│ host/          FastAPI routers, UI templates,          │
│                CLI entry points, background jobs.      │
│                Talks to application/ and contracts/.   │
├────────────────────────────────────────────────────────┤
│ application/   Domain logic — solvers, scheduling,     │
│                equity computation, workflow engine.    │
│                No I/O; pure functions where possible.  │
├────────────────────────────────────────────────────────┤
│ adapters/      Postgres, KMS providers, notification   │
│                channels, OIDC identity, backup + witness  │
│                backends, LLM adapter. ARC-001: adapters     │
│                MUST NOT import host/.                  │
├────────────────────────────────────────────────────────┤
│ contracts/     Type contracts + closed-set catalogues. │
│                v1.1/ corpus — the wire contract with   │
│                every partner (installers, plugins,     │
│                external audits).                       │
└────────────────────────────────────────────────────────┘
```

## Corpus-first discipline

The `v1.1/` directory is the corpus — the source of truth for all cross-system contracts. Files there are versioned as `v1.1` and carry per-package manifests + per-file hashes.

The corpus contains:

* **API surface** — `v1.1/docs/api/api-v1.1.md` + regenerated `openapi.yaml`.
* **Data contracts** — every migration `v1.1/docs/release/migrations-v1.1.md`.
* **Security posture** — `v1.1/docs/security/security-v1.1.md`: roles, permissions, RLS policies.
* **Plugin extensions** — `v1.1/docs/architecture/plugin-extensions-v1.1.md`: what subsystems host third-party bundles.
* **Commercial** — `v1.1/docs/commercial/licensing-v1.1.md`: trust chain, subkeys, revocation format.
* **Closed sets** — `v1.1/package-manifest-v1.1.json`: cardinality-pinned catalogues (roles, permissions, error codes, migrations, signing keys, ...).

Any change to code that has a corpus counterpart must update both together. `tests/arc/` enforces this via cross-corpus integrity checks.

## Multi-tenant isolation

Every tenanted row carries `tenant_id`. Postgres row-level security (RLS) enforces isolation via `app.current_tenant`:

```
CREATE POLICY tenant_isolation
  ON <table>
  USING (tenant_id = current_setting('app.current_tenant')::uuid);
```

Middleware sets `app.current_tenant` on the connection at request-scoping. There is no bypass path except the `platform_admin` role's break-glass audit-logged escalation.

Some tables additionally use `department_isolation` policies for BPP-EQUITY (P0) — feedback threads, roster-area-scoped BPP-EQUITY artefacts. The two policies combine additively.

Both policies use `FORCE ROW LEVEL SECURITY` so table owners cannot bypass by table ownership.

## Closed sets

Cardinality-pinned enumerations. Changing a closed set requires:

1. Migration seeding the new value.
2. Corpus documentation update.
3. `tests/arc/test_closed_set_integrity.py` cardinality bump.
4. `v1.1/package-manifest-v1.1.json` update.

The current sizes live in `v1.1/package-manifest-v1.1.json` under `closed_sets`, and `tests/arc/test_closed_set_integrity.py` fails if the product and the manifest disagree. ⚠️ The table below is checked against that manifest by `tests/docs/test_public_docs_describe_the_real_access_model.py` — an earlier version of this page was captioned "as of v1.6.2 close-out" and had drifted to **10 roles, 123 permissions and 157 migrations** against a real 15, 171 and 398, while reading as authoritative reference.

| Set                       | Cardinality |
| ------------------------- | ----------- |
| `errorCodes`              | 27          |
| `roles`                   | 15          |
| `permissions`             | 171         |
| `pluginSubsystems`        | 9           |
| `migrations`              | 398         |
| `testDomains`             | 22          |
| `severityClasses`         | 4           |
| `signingKeys`             | 5           |
| `roomSpaceType`           | 2           |
| `spaceCategory`           | 4           |
| `spaceAssignment`         | 2           |
| `roomState`               | 2           |
| `jobPlanActivityCategory` | 4           |
| `roleSpaMinimum`          | 12          |

## The CP-SAT solver

The rostering solver is Google's CP-SAT via `ortools`. Universe input is built by `host/solver/universe_builder_v12.py` (`build_universe_v12`) from live database state. The solve step (`_solve_rostering`) formulates the constraint model, invokes the solver, and returns candidate assignments with per-candidate score breakdowns.

Determinism: the solver is seeded per solve; two consecutive solves against the same universe input produce identical output. `roster.solve.determinism_verified` audit events record this post-solve. Drift emits `roster.solve.determinism_drift` (records-only in v1; comparator lands in v1.5+).

## The witness pattern

Audit seals + backup destinations both use "witness" adapters — a third party (a filesystem, an object store with lock, a KMS-signed record) that the platform runtime cannot retroactively modify. In v1 the audit-seal witness is `LocalFs`; S3 Object Lock is a fail-loud stub graduating in v1.5+.

The off-system witness lives in `host/audit/witness.py` (`LocalFileWitness`, `S3ObjectLockWitness`), verified by `bin/rostaire-witness-verify`; backup encryption is an adapter at `adapters/backup_encryption/`. Both follow the same "registry-driven, honest signals over silent fallback" pattern.

## Registry-driven extension points

Where the platform admits third-party extension (plugins, notification channels, backup destinations, KMS providers, LLM adapters, dashboard widgets), the pattern is:

1. Contract module in `contracts/` declares the protocol.
2. Adapters under `adapters/<name>/` implement the protocol.
3. `host/bootstrap/*` reads a registry of installed adapters at boot and wires them into subsystems.
4. Plugin bundles (v1.6 P25) are signed via `R-PLUGIN-2026` and verified against the trust anchor before load.

The nine plugin subsystems: `database`, `identity`, `notification`, `dashboard`, `report`, `llm`, `language`, `entitlement`, `widget` (added in v1.6 P25).

## The audit chain

Every state-changing action emits an `audit_events` row. Rows link via `row_hash = sha256(prev_hash || canonical_bytes(this_event))`, where the canonical bytes are RFC 8785 over the whole event, not just its payload. The nightly seal signs a canonical summary of the day's chain head under `R-AUDIT-SEAL-2026`. The chain-status page reports the daily chain-verification runs.

See [Audit and compliance](/part-2-running-resouris/audit-and-compliance.md) for the operational picture.

## The rostering job queue

Roster generation, sickness-batch resolution, backup, restore, reschedule proposals — all flow through `roster_generation_jobs` with a `kind` closed set. Consumer workers pick jobs off the queue and update state. Failure produces an error report attached to the job; retries are opt-in.

## The corpus of what's deferred to v1.5+

Held as `docs/operations/stub-inventory.md` — the honest catalogue of stubs, fail-loud responses, and enhancements deferred. If you're building against ReSouris and a feature you need has a fail-loud stub, that page is the reference for what shipped vs. what didn't.

## Where to read next

* Wire contracts for licences + revocations: `v1.1/docs/commercial/licensing-v1.1.md`.
* Migrations detail: `v1.1/docs/release/migrations-v1.1.md`.
* Solver internals: `application/rostering/` module docstrings.
* Corpus discipline: `v1.1/package-manifest-v1.1.json` and the `tests/arc/` suite.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.resouris.com/reference/architecture.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
