# Resource Registry — API Management (Contexts & Types)

## Context Management — base path `/contexts`

### Authorization & Token Selection

| Action | HTTP | Path | Query Parameters | IS-M / Infra-M | IS-Admin | Ctx-M | IS-Editor | IS-Contrib | IS-Member |
|--------|------|------|------------------|:--------------:|:--------:|:-----:|:---------:|:----------:|:---------:|
| `List` | GET | `/contexts` | `limit`, `offset`, `lifecycle`, `metadataPolicy` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Execute JSON Query` | QUERY | `/contexts` (`POST /contexts/query` fallback) | `limit`, `offset`, `lifecycle`, `metadataPolicy` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Read` | GET | `/contexts/{context-uuid}` | `depth`, `metadataPolicy` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Read Current` | GET | `/contexts/@current` | `depth`, `metadataPolicy` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Create` | PUT | `/contexts/{context-uuid}` | `metadataPolicy` | ✅ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
| `Tracked Update` | PATCH | `/contexts/{context-uuid}` | `metadataPolicy` | ✅ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
| `Read History` | GET | `/contexts/{context-uuid}/history` | *None* | ✅ | ✅ | ⚠️ | ❌ | ❌ | ❌ |
| `Delete` | DELETE | `/contexts/{context-uuid}` | *None* | ✅ | ❌ | ⚠️ | ❌ | ❌ | ❌ |

**Token Scope Rule:**

| Operation Group | Token must be issued for |
|-----------------|--------------------------|
| `List`, `Execute JSON Query` | No specific target Context is required; authorization is evaluated from the token role |
| `Read` | No specific target Context is required: every role reads every live Context with any token (see *Context visibility* below) |
| `Read History` | `IS-M` and `Infra-M`: any Context, live or deleted, with any token. `IS-Admin`: any live Context, with any token. `Ctx-M`: a token for the target Context or any ancestor of it |
| `Read Current` | The current token Context (token-bound endpoint semantics) |
| `Create`, `Tracked Update`, `Delete` | **Parent Context** of the target Context. A `Tracked Update` that changes `parent` requires instead the **closest common ancestor** of the current and the new parent (see *Reparenting and Renaming Constraints*). *(Root exception: only `IS-M` and `Infra-M` may create, update, or delete the Root, using a token issued for that Root Context itself.)* |

### HTTP Contract

| Action | Additional Request Headers | Response Headers | Success HTTP Code | Error HTTP Code |
|--------|----------------------------|------------------|-------------------|-----------------|
| `List` | `Accept` | `Content-Type`, `Link`, `X-Warning` | `200 OK` | `403 Forbidden` |
| `Execute JSON Query` | `Content-Type`, `Accept` | `Content-Type`, `Link`, `X-Warning` | `200 OK` | `400 Bad Request`, `403 Forbidden` |
| `Read` | `Accept` | `Content-Type`, `Link`, `X-Warning` | `200 OK` | `400 Bad Request`, `403 Forbidden`, `404 Not Found` |
| `Read Current` | `Accept` | `Content-Type`, `Link`, `X-Warning`, conditional `X-Result-Scope` / `X-Resolved-Context-Path` | `200 OK` | `403 Forbidden`, `404 Not Found` |
| `Create` | `Content-Type`, `Accept` | `Content-Type`, `Link`, `X-Warning`, conditional `X-Result-Scope` / `X-Resolved-Context-Path` | `201 Created` / `200 OK` | `400 Bad Request`, `403 Forbidden`, `404 Not Found`, `409 Conflict` |
| `Tracked Update` | `Content-Type: application/merge-patch+json`, `Accept` | `Content-Type`, `Link`, `X-Warning`, conditional `X-Result-Scope` / `X-Resolved-Context-Path` | `200 OK` | `400 Bad Request`, `403 Forbidden`, `404 Not Found`, `409 Conflict` |
| `Read History` | `Accept` | `Content-Type`, `Link`, `X-Warning`, conditional `X-Result-Scope` / `X-Resolved-Context-Path` | `200 OK` | `400 Bad Request`, `403 Forbidden`, `404 Not Found` |
| `Delete` | *None* | `X-Warning`, conditional `X-Result-Scope` / `X-Resolved-Context-Path` | `204 No Content` | `400 Bad Request`, `403 Forbidden`, `404 Not Found`, `409 Conflict` |

### Operational Notes

- **Context visibility:** The tree of live Contexts is visible to every Resource Registry role, whatever the Context of its token. The token Context decides where a caller may operate, never which live Contexts it may see. List, query, point read, and point read with `depth` therefore all expose the same live tree to every role. What remains restricted is the cemetery (`IS-M` and `Infra-M` only), the Context History (see `Read History`), and the identity fields of `metadata`, redacted for non-administrative roles. Context reads are not subject to the `effectiveState` barrier: a caller whose token Context is `suspended` or `created` still reads it (for instance through `GET /contexts/@current`) and can see why its instance operations are refused; the barrier applies to instance operations only.
- **Global live collection semantics:** `GET /contexts`, `QUERY /contexts`, and their `/access` mirrors operate on the complete live Context collection for every Resource Registry role. The token carries the role used to authorize the endpoint but its Context does not bound collection visibility. Results are flat and paginated rather than a recursive tree rooted at the token Context; clients use point reads with `depth` when they need a rooted hierarchy. This global live view is intentional and emits no `X-Result-Scope` header. The `lifecycle` parameter remains the independent lifecycle boundary: only `IS-M` and `Infra-M` may request `deleted` or `all`, while every other role is restricted to `live`. Metadata visibility continues to follow the caller's role.
- **Token path resolution:** Every operation that derives an operational Context from the caller's token matches the token path, in this order, against: the current `path` of a live Context; the `previousPaths` of a live Context; the `path` of a `deleted` Context (cemetery). A live Context always wins over a deleted one with the same path. A path that matches only a deleted Context returns `403 Forbidden`; on a read that returns the token Context (`Read Current`), a path that matches nothing returns `404 Not Found`. A mutation whose token does not resolve to the Context it requires returns `403 Forbidden`, whatever the reason: what is missing is the authority over that Context, not the target of the request. The same holds for every direct instance operation, read or write, and for stored Query Template execution, whatever the caller's role: a token path that matches only a deleted Context, or no Context at all, returns `403 Forbidden`. The creation of the Root is the one operation whose token names a Context that does not exist yet: it is not affected by a deleted Root with the same path. A fallback match has the same authorization meaning as a direct match and emits `X-Result-Scope: previous-path` plus `X-Resolved-Context-Path` containing the current path. A direct match emits neither header. This shared behavior applies to the Context operations that use the token Context (`Read Current`, `Read History` by `Ctx-M`, and parent-scope mutations; not `List`, `Execute JSON Query`, or `Read` by UUID), direct instance operations, and stored Query Template execution through both the management and `/access` surfaces. Query Template definition management remains global and does not resolve an operational Context.
- **Read shape and `depth`:** `GET /contexts/{context-uuid}` and `GET /contexts/@current` always return a flat JSON array with the requested Context first, followed by its descendants level by level: up to `depth` levels, or all of them with `depth=-1`. With `depth=0` (the default) the array contains only the requested Context. Every element is a complete Context representation whose `parent` and `children` are summaries, as for any Context. `Create` and `Tracked Update` return instead the created or updated Context as a single JSON object, not an array.
- **Read:** `GET /contexts/{context-uuid}` resolves a Context explicitly by UUID. The distinction between `403` and `404` is intentional and operationally significant:
  - `404 Not Found`: no Context with the specified UUID exists — neither among live contexts nor in the cemetery.
  - `403 Forbidden`: a Context with the specified UUID exists but is inaccessible to the caller. This occurs only when the Context is in the **deleted** state (cemetery) and the caller does not hold a manager role (`IS-M` / `Infra-M`). A live Context is readable by every role (see *Context visibility*).
- **Read Current:** `GET /contexts/@current` applies the same `403` vs `404` distinction as `Read`, but with a key difference: the target Context is not specified explicitly by the caller — it is **resolved implicitly from the authorization token itself**.
  - `404 Not Found`: the token's path cannot be resolved to any known Context — neither among live contexts nor in the cemetery. The known root causes are:
    - **IAM / IS bootstrap race:** the IAM has registered a Context path and issued a token for it, but the corresponding Context record has not yet been created in the IS (or the creation failed). This is inherently transient: in a correctly sequenced deployment the IS Context is provisioned before the IAM path is activated. If this 404 appears in production it signals an integration error.
    - **Token carries an unknown path:** the path embedded in the token matches neither a current `path` nor any retained `previousPaths` entry.
  - `403 Forbidden`: the token's path resolves to a Context in the **deleted** state (cemetery), whatever the caller's role: `@current` never designates a deleted Context. This is inherently a transient edge case — it can only arise in the window between Context deletion and the expiration or IAM-side revocation of the caller's outstanding token. In a correctly integrated deployment, token revocation is triggered on Context deletion, making this state short-lived.
- **Context lifecycle permissions:** `IS-M` and `Infra-M` can manage every Context. `Ctx-M` can manage a target only from its immediate parent Context (or, for a reparenting, from the closest common ancestor of the current and the new parent) and receives filtered metadata; it can never create, update, or delete the Context represented by its own token. `IS-Admin` has universal visibility across live Contexts but cannot access the cemetery or create, update, or delete Contexts.
- **The Root Context Exception:** The Context topology is either empty or a strict single-root tree. Only its unique Root Context may have `parent: null`. `IS-M` and `Infra-M` are the only roles that may create, update, or delete the Root, using a token issued for that exact Root Context. When the topology is empty, either role may create the Root. Once a Root exists, any attempt to create another parentless Context returns `409 Conflict`.
- **Creation via `PUT` vs `POST`:** The API uses `PUT` instead of `POST` for Context creation because the target Context UUID must be explicitly provided in the URL path by the client (e.g., `/contexts/{context-uuid}`). Accepting an internal UUID exclusively in the body of a `POST` request to the base collection endpoint would be an architectural anti-pattern. Note that the UUID must also be present in the JSON body representation; the server performs a strict double-check between the URL path and the body representation to ensure consistency.
- **`PUT /contexts/{context-uuid}` semantics (idempotent creation):** `PUT` is strictly for Context creation. It requires defining `id`, `name`, and `parent`. A `parent` that does not exist returns `404 Not Found`; a `parent` that is in the cemetery returns `409 Conflict`. The token is resolved first: a token whose path matches only a deleted Context returns `403 Forbidden` before the `parent` is examined, so the `409` applies to a caller whose token resolves to a live Context, for example `IS-M` with a token for the root Context. The checks follow this order: role, token resolution (`403`), body validation (`400`), `parent` (`404` or `409`), token scope (`403`); a caller whose token is not the one required therefore still receives `404` or `409` for a missing or deleted `parent`. The `state` field is system-managed upon creation (forced to `created`); supplied `state` and `previousPaths` fields are ignored and reported through `X-Warning`. Historical paths can be introduced only through authenticated `PATCH`. To strictly adhere to HTTP `PUT` idempotency, sending the exact same creation payload multiple times will return `201 Created` the first time and `200 OK` (no-op) on subsequent identical requests. However, if a client attempts to execute a `PUT` on an existing Context with a payload that is incongruent with the existing representation, the system will reject it with `409 Conflict`. The comparison covers the fields the caller can read: the additional fields of a Context take part in it only for `IS-M` and `Infra-M`; for any other role the stored ones are ignored and left untouched. Creating a Context whose current path matches another Context's retained previous path reclaims it, as every newly established current path does (see *Live path ownership and reclamation* below): the retained value is removed atomically and the removal is recorded in the affected Context's History. All legitimate state modifications must be performed exclusively via the `Tracked Update` (`PATCH`) endpoint.
- **`PATCH /contexts/{context-uuid}` semantics (Tracked Update):** Uses the `application/merge-patch+json` media type ([RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396)) to partially update the Context. The body must be a JSON object. It processes changes only to `state`, `name`, `parent`, and `previousPaths`. The other fields follow the general system-field rule (see [System Fields](07-api-foundations-and-roles.md)): the server-computed fields `metadata`, `path`, `effectiveState`, and `children` are dropped without warning whatever their value, so that a Context just read can be submitted back; `type` and `id` are optional but, when present, must identify the Context addressed by the path (`type` must be `Context`), otherwise `400 Bad Request`; any other top-level field is ignored and reported through an `X-Warning` header. `state`, when present, must be the string `active` or `suspended` (`created` and `deleted` are system-managed). `name`, when present, must be a valid non-null Context name. `parent`, when present, must be an object containing a textual UUID `id`; the unique Root may also receive JSON null as a no-op assertion of its existing parentless state. Setting `parent: null` on any non-Root Context would create a second root and therefore returns `409 Conflict`; every accepted reparenting must also keep the hierarchy acyclic: moving a Context under itself or under one of its own descendants returns `409 Conflict`. A new `parent` that does not exist returns `404 Not Found`, and one that is in the cemetery returns `409 Conflict`, as on creation. `previousPaths`, when present, must be null or an array of absolute path strings and replaces the complete retained ordered set; null and `[]` clear it, duplicate values within the submitted array are normalized, and administrators may add historical paths for remediation. Reasserting a value already retained by the same Context is a no-op, while adding a value owned by another live Context as either its current `path` or one of its `previousPaths` returns `409 Conflict`. A value equal to the Context's own current `path` returns `409 Conflict` too: a path is either the current path or a previous path of a Context, never both. When one patch also renames or reparents the Context, the explicit replacement runs first and the outgoing current path is then appended. Every descendant whose path changes receives its own outgoing path atomically. The backend writes history and modification metadata only for effective tracked-field changes, so an empty patch, a same-value patch, or a patch containing only ignored fields is a successful `200 OK` no-op. The checks follow this order: role, token resolution (`403`), body validation (`400`), target (`404`), cemetery (`409`), new `parent` (`404` or `409`), token scope (`403`).
- **Live path ownership and reclamation:** Across live Contexts, one absolute path string identifies at most one Context, either as its current `path` or as one of its `previousPaths`. Two live Contexts can never share a current path: a creation, rename, or reparent that would give a Context the current path of another live Context (for example, two siblings with the same `name`) returns `409 Conflict`. A newly established current path is authoritative over retained former paths: Context creation, rename, reparent, and descendant path recomputation atomically remove that value from every retained `previousPaths` set, including the same Context when it returns to one of its former paths. Each automatic removal records a History event on the affected Context. An explicit `previousPaths` update never reclaims an existing identity; conflicting ownership is rejected with `409 Conflict`.
- **Reparenting and Renaming Constraints:** Changing the `parent` Context or the `name` of a Context shifts the structural addressing and IAM resolution of an entire hierarchical branch. To ensure security consistency and prevent accidental "live exposure", reparenting or renaming is strictly allowed only when the Context's **local `state`** is non-`active` (e.g., `suspended` or `created`). The check uses the persisted `state`, not a `state` submitted in the same `PATCH`: an `active` Context is suspended first and moved or renamed with a second request. Attempting to move or rename a Context while its local `state` is `active` will result in a `409 Conflict`.
- **Reparenting authorization:** A move touches two positions in the tree, so the token must be issued for the Context that contains both: the closest common ancestor of the current parent and the new parent. When the new parent lies below the current parent, that ancestor is the current parent itself, which is the ordinary parent-token rule. A token for any other Context, a higher ancestor included, returns `403 Forbidden`. For example, moving `/root/A/X` under `/root/B` requires a token for `/root`, as does moving it directly under `/root`; moving it under `/root/A/C` requires a token for `/root/A`. The rule applies to every role allowed to update Contexts (`IS-M`, `Infra-M`, `Ctx-M`); a `Context-Manager` therefore moves Contexts only within the subtree whose token it holds.
  - *Architectural Rationale:* Why check the local `state` instead of the `effectiveState`? Consider a child Context whose local state is `active`, but its parent is `suspended`. Due to inheritance, the child's `effectiveState` is naturally `suspended`. If the system permitted the move based solely on the `effectiveState`, an administrator could move this child under a new, `active` parent. Because its local state was already `active`, the child Context would instantly "wake up" and become fully operational immediately upon transfer, potentially exposing the entire subtree to live network traffic without warning. Forcing the local state to be manually set to `suspended` before the move guarantees that the Context remains safely offline during and after the migration, requiring a deliberate secondary action to reactivate it.
- **Read History:** `GET /contexts/{context-uuid}/history` is a pure read-only endpoint that returns a flat JSON array of **`Event`** objects (sorted in reverse-chronological order, newest first). The path identifier must be a valid UUID; malformed values return `400 Bad Request`. Query parameters are unsupported and ignored with an `X-Warning` header; in particular, `lifecycle` cannot select cemetery routing. Lifecycle visibility is derived exclusively from role: `IS-M` / `Infra-M` can read live or deleted Context history, while `IS-Admin` and `Ctx-M` can read live Context history only. This provides a complete audit trail of the Context's lifecycle. Each `Event` explicitly captures the transition details:
  - `who`: The principal who triggered the change. It is returned in full to `IS-M`, `Infra-M`, and `IS-Admin`, and as `"[REDACTED]"` to `Ctx-M`, consistently with metadata identity visibility. This is an explicit sensitivity rule for the system-produced Context History field; it does not classify arbitrary application fields named `who` as sensitive.
  - `what`: The transition value (e.g., `created -> active`).
  - `where`: The specific field modified (e.g., `state` or `name`).
  - `when`: The timestamp of the occurrence.
  - `how`: The triggering mechanism or API method. It is `PATCH` on the Context the request addressed, and `PATCH (cascade from {context-uuid})` on every descendant the change propagated to, where `{context-uuid}` identifies the Context whose `PATCH` caused the propagation (an ancestor, not necessarily the immediate parent). When a Context loses one of its `previousPaths` because another Context took that path as its current one, the event on the losing Context has `what: removed {path}` and `how: PUT (path reclaimed by {context-uuid})` or `how: PATCH (path reclaimed by {context-uuid})`, according to the operation that established the path on the reclaiming Context.

  Changes to `previousPaths` are recorded independently: direct and cascaded path changes add the outgoing path, explicit replacements record each addition and removal, and a path reclaimed through a creation, rename, reparent, or descendant path recomputation records the removed path and the UUID of the reclaiming Context. These events use `where: previousPaths` and do not replace the separate `name`, `parent`, or `path` events. Within one `PATCH`, the events of an explicit `previousPaths` replacement are recorded before the `state`, `name`, and `parent` events of the same request.
  
  *Crucial Auditing Feature:* This history actively records changes to the `effectiveState`. If an ancestor Context is suspended, the backend cascades the suspension down the tree, writing an `Event` into the history of every descendant Context (e.g., `where: effectiveState`, `how: PATCH (cascade from {context-uuid})`). This guarantees that administrators can instantly understand why a specific Context went offline without manually traversing the parent hierarchy.
- **Context Deletion (The Cemetery):** `DELETE` permanently removes the Context from active listings and transitions it to a `deleted` state in the historical "cemetery" (from which it cannot be resurrected, though a new Context with the same path but a different UUID can be created). The path identifier must be a valid UUID; malformed values return `400 Bad Request`. The target must exist in the live topology, so deleting an unknown UUID returns `404 Not Found`, while deleting a Context that is already in the cemetery is a no-op that returns `204 No Content` for `IS-M` and `Infra-M`: the requested state is already in place, exactly as a repeated identical `PUT` is a `200 OK` no-op, so they can repeat a `DELETE` safely. Any other role receives `403 Forbidden`, because the cemetery is not visible to it. A non-Root target requires an administrative token for its current immediate parent. Deleting the Root is restricted to `IS-M` and `Infra-M` and requires a token for the Root itself. Unsupported query parameters are ignored and reported through `X-Warning`. Deletion fails with `409 Conflict` if the Context still contains any child Contexts or graph instances. Deleting the empty Root returns the topology to the valid empty bootstrap state; the next Context creation must establish one new unique Root. Collection reads (`GET /contexts`, `QUERY /contexts`) support explicit cemetery visibility via the `lifecycle` parameter: `live` (default), `deleted`, `all`. Access to cemetery-inclusive modes (`deleted`, `all`) is restricted to `IS-M / Infra-M`; for all other roles, requesting these modes returns `403 Forbidden`.
- **Execute JSON Query (QBE):** `QUERY /contexts` exposes the powerful JSON Query-By-Example engine specifically for the Contexts collection. Instead of polluting the `GET /contexts` endpoint with hardcoded query parameters, clients can submit a JSON payload matching the desired Context state. This allows advanced filtering across the entire Context representation (e.g., querying `{"effectiveState": "suspended"}` to find all offline Contexts). Predicates involving `metadata.createdBy` or `metadata.lastUpdateBy` follow the shared caller-visible query rule: `IS-M`, `Infra-M`, and `IS-Admin` compare stored identities, while every other role compares the visible `"[REDACTED]"` value. The endpoint otherwise shares the exact same semantics, operators, and `_emit` projection capabilities as the instance query engine.
  - *Lifecycle visibility contract:* `lifecycle=live` returns only active-topology contexts (default); `lifecycle=deleted` returns only cemetery contexts; `lifecycle=all` returns the union. The server applies role checks before query evaluation.
- **Non-`active` Context behavior:** When a Context is `created` or `suspended`, `IS-M`, `Infra-M`, and `IS-Admin` can perform instance operations regardless of that state. A `deleted` Context allows no instance operation for any role (`403 Forbidden`; see the matrix below). `Ctx-M` can do so inside its managed subtree when its credentials address the operational Context. Roles with lesser administrative scope (`IS-Editor`, `IS-Contrib`, and `IS-Member`) are strictly blocked from performing any instance operation in non-`active` Contexts and receive `403 Forbidden`.

### Context Lifecycle State Machine

A Context transitions through a strict lifecycle governed by the API operations. The state machine ensures that environments can be safely prepared (`created`), paused (`suspended`), actively used (`active`), and securely destroyed (`deleted`).

> **Architectural Metaphor (Founder, CEO and Division Manager):**
> To understand the authorization split between parent and child Contexts during lifecycle operations, consider a corporate metaphor.
>
> - **The Root Context Bootstrap:** An entrepreneur has a business idea. They found a company, and by the physical act of founding it, they become its CEO. This perfectly explains the Bootstrap Exception: an `IS-M` creating the **Root** Context uses a token for that exact new boundary.
> - **The Child Contexts:** The CEO (the admin of the **Root** Context) decides to create a new company division (`PUT /contexts/child`). The CEO creates it (which starts in `created`), eventually activates it (`active`), temporarily suspends it (`suspended`), or permanently closes it (`deleted`). However, the CEO assigns a specific manager (the admin of the *child* Context) to run the division. This local manager is the one who actually populates the division with employees and rules (creating/updating instances inside the `created` Context). Once the local manager has prepared everything, the CEO officially declares the division open (`PATCH /contexts/child` to `active`). This perfectly illustrates why creating/activating a Context requires parent-level authority, while populating it requires local authority.

```mermaid
---
config:
  theme: neutral
  flowchart:
    edgeLabelBackground: "#ffffff"
---
stateDiagram-v2
    [*] --> created : Create (PUT)
    created --> active : Tracked Update (PATCH)
    created --> suspended : Tracked Update (PATCH)
    
    active --> suspended : Tracked Update (PATCH)
    suspended --> active : Tracked Update (PATCH)
    
    created --> deleted : Delete (DELETE)
    active --> deleted : Delete (DELETE)
    suspended --> deleted : Delete (DELETE)
    
    deleted --> [*] : Terminal State (Cemetery)
    
    note right of deleted
        Deletion is permanently irreversible.
        Requires empty Context 
        (0 instances, 0 children).
    end note
```

**State Transition Matrix**
Transitions of a non-Root Context require a token for its **parent Context** and an authorized Context-lifecycle role (`IS-M`, `Infra-M`, or `Ctx-M`). Root creation and every Root transition require a Root token and are restricted to `IS-M` and `Infra-M`.

| Current State | API Operation | Next State | System Behavior & Validation |
|---------------|---------------|------------|------------------------------|
| (Does not exist) | `PUT` | **`created`** | The system provisions the Context. The state is forced to `created`. |
| `created` | `PATCH` | **`active`** | Opens the Context to operational users (`IS-Editor`, `IS-Contrib`, `IS-Member`). |
| `created` | `PATCH` | **`suspended`** | Configures the Context as suspended before it was ever opened; it stays closed to `IS-Editor`, `IS-Contrib`, and `IS-Member`. |
| `active` | `PATCH` | **`suspended`** | Temporarily blocks `IS-Editor`, `IS-Contrib` and `IS-Member` from performing operations. Useful for maintenance or administrative locks. |
| `suspended` | `PATCH` | **`active`** | Restores normal operations for all roles. |
| *Any non-deleted* | `DELETE` | **`deleted`** | Moves the Context to the historical cemetery. **Strict validation:** the Context MUST NOT contain any child Contexts or instances, otherwise the request is rejected with `409 Conflict`. |

> **Immutability of the Cemetery:** Once a Context reaches the `deleted` state, it cannot be resurrected or updated: a `PATCH` on it returns `409 Conflict`, whatever the caller's role, because the Context exists but is in a terminal state. The same holds when it is named as the destination of an `Add to Context`, and its UUID stays occupied: a `PUT` that would create a Context with that UUID returns `409 Conflict` too. A repeated `DELETE` is the only mutation that succeeds, as a `204 No Content` no-op for `IS-M` and `Infra-M` (`403 Forbidden` for any other role). However, because it physically vacates the active topology, an administrator can create a *brand new* Context using the same `name` and `parent` (resulting in the same hierarchical path), but it will possess a completely new, unique UUID.

### Inherited Operational State (`effectiveState`)

To support maintenance operations at scale without altering the physical state of thousands of sub-environments, the system strictly separates local configuration (`state`) from operational evaluation (`effectiveState`). The `effectiveState` is a **read-only field**. The system recomputes and atomically cascades it to all descendant Contexts whenever the Context's own `state` or any ancestor's `state` changes.

**State Evaluation Matrix:**

| Parent's `effectiveState` | Context's Local `state` | Resulting `effectiveState` |
|---------------------------|-------------------------|----------------------------|
| *(None / Root Context)*   | *(Any state)*           | **`= Local state`**        |
| *(Any state)*             | `created`               | **`created`**              |
| *(Any state)*             | `deleted`               | **`deleted`**              |
| `created`                 | `suspended` or `active` | **`suspended`** *(inherited)* |
| `suspended`               | `suspended` or `active` | **`suspended`** *(inherited)* |
| `active`                  | `suspended`             | **`suspended`**            |
| `active`                  | `active`                | **`active`**               |

> **Rationale for `created` inheritance:** If a child Context has been explicitly advanced to `active` or `suspended` (by a caller holding its parent's token), it is no longer a blank slate. Therefore, its parent's `created` state acts purely as an operational lock, yielding an effective `suspended` state to freeze operations without reverting or obscuring the child's explicitly configured local status.

> **Architectural Rationale (Offline Restructuring):**
> There are **no restrictions** on Context Lifecycle operations (`PUT`, `PATCH`, `DELETE` for Contexts) based on the parent's state. `IS-M`, `Infra-M`, and an authorized `Ctx-M` may build and configure an entire nested topology while an ancestor Context is still in a `created` or `suspended` state. This enables *Offline Restructuring* while the effective-state algorithm keeps operational users out.

This architectural design allows an administrator to securely lock down an entire branch (e.g., a VO and its 50 VREs) via a single state update on the parent node, without destroying the historical configuration state of the children. When an ancestor is placed in a `suspended` or `created` state, the system cascades the change to all descendants in the same atomic operation: the `effectiveState` of every descendant whose local `state` was `active` becomes `suspended`, so no descendant can be read or written as active once the lock is in place.

**The Universal `effectiveState` Rule for Operations:**
Throughout this documentation and the API authorization rules, any restriction mentioning that operations are blocked in **non-`active`** Contexts **always refers strictly to the `effectiveState`**. The following matrix illustrates whether a role is disabled by the Context's state lock when attempting an operation on a graph instance (such as creating or reading a **Resource** instance). The specific privileges (e.g., who can write vs read) remain strictly governed by the API reference tables; this matrix solely defines whether the Context's `effectiveState` acts as an absolute barrier (`Disabled`, yielding a `403 Forbidden`) or if it allows the role to proceed (`Enabled`). This evaluation does NOT apply to Context Lifecycle operations (like updating a Context via `PATCH`).

| `effectiveState` | IS-M / Infra-M | IS-Admin | Ctx-M | IS-Editor | IS-Contrib | IS-Member |
|------------------|----------------|----------|-------|-----------|------------|-----------|
| **`created`** | ✅ Enabled | ✅ Enabled | ✅ Enabled | ❌ Disabled (`403`) | ❌ Disabled (`403`) | ❌ Disabled (`403`) |
| **`active`** | ✅ Enabled | ✅ Enabled | ✅ Enabled | ✅ Enabled | ✅ Enabled | ✅ Enabled |
| **`suspended`** | ✅ Enabled | ✅ Enabled | ✅ Enabled | ❌ Disabled (`403`) | ❌ Disabled (`403`) | ❌ Disabled (`403`) |
| **`deleted`** | ❌ Disabled (`403`) | ❌ Disabled (`403`) | ❌ Disabled (`403`) | ❌ Disabled (`403`) | ❌ Disabled (`403`) | ❌ Disabled (`403`) |

*(Note: The `effectiveState` barrier applies equally to reads and writes for `IS-Editor`, `IS-Contrib`, and `IS-Member`. Administrative roles are not restricted by Context state except in `deleted` Contexts).*

> **Client Guidance:** The `effectiveState` is exposed to allow API clients to determine in advance whether their instance operations (whether executing a read query or a mutation) will be accepted (e.g., to disable buttons in a GUI), or to understand the reason for a rejection a posteriori. Any attempt to operate when the matrix evaluates to `Disabled` will be rejected by the system.
> *(Note on `deleted` contexts: While all operations on graph instances issued with a token of a `deleted` Context are universally blocked for everyone (`403`), and an `Add to Context` that names a `deleted` Context as its destination returns `409 Conflict`, global administrators can still inspect deleted Contexts through Context collection/read APIs using the `lifecycle` visibility model and via `GET /contexts/{context-uuid}/history`.)*

## Type Management — base path `/types`

### Authorization & Token Selection

| Action | HTTP | Path | Query Parameters | IS-M / Infra-M | IS-Admin | Ctx-M | IS-Editor | IS-Contrib | IS-Member |
|--------|------|------|------------------|:--------------:|:--------:|:-----:|:---------:|:----------:|:---------:|
| `Execute JSON Query` | QUERY | `/types` (`POST /types/query` fallback) | `limit`, `offset`, `depth`, `metadataPolicy` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Create` | PUT | `/types/{type-name}` | `metadataPolicy` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `Read` | GET | `/types/{type-name}` | `depth`, `metadataPolicy` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `Delete` | DELETE | `/types/{type-name}` | *None* | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |

**Token Scope Rule:**

| Operation Group | Token must be issued for |
|----------------|---------------------------|
| `Execute JSON Query`| No specific target Context is required; authorization is evaluated from the token role |
| `Create`, `Delete` | No specific target Context is required; authorization is evaluated from the token role (requires `IS-M` / `Infra-M`) |
| `Read` | No specific target Context is required; authorization is evaluated from the token role |

### HTTP Contract

| Action | Additional Request Headers | Response Headers | Success HTTP Code | Error HTTP Code |
|--------|----------------------------|------------------|-------------------|-----------------|
| `Execute JSON Query` | `Content-Type`, `Accept` | `Content-Type`, `Link`, `X-Warning` | `200 OK` | `400 Bad Request`, `403 Forbidden` |
| `Create` | `Content-Type`, `Accept` | `Content-Type`, `X-Warning` | `201 Created` / `200 OK` | `400 Bad Request`, `403 Forbidden`, `409 Conflict` |
| `Read` | `Accept` | `Content-Type`, `Link`, `X-Warning` | `200 OK` | `400 Bad Request`, `403 Forbidden`, `404 Not Found` |
| `Delete` | *None* | `X-Warning` | `204 No Content` | `403 Forbidden`, `404 Not Found`, `409 Conflict` |

### Operational Notes

```eval_rst
- **Creation via ``PUT`` vs ``POST``:** The API uses ``PUT`` instead of ``POST`` for Type creation because the Type identifier (``type-name``) must be explicitly provided in the URL path by the client (e.g., ``/types/{type-name}``). Accepting an identifier exclusively in the body of a ``POST`` request to the base collection endpoint would be an architectural anti-pattern. Note that the ``name`` must also be present in the JSON body representation; the server performs a strict double-check between the URL path and the body representation to ensure consistency.
- **Internal ``id`` ownership during creation:** The ``id`` field inside the Type JSON represents its internal global UUID. The REST endpoint URL strictly uses the readable ``type-name``, never the UUID. If a client includes an ``id`` field in the creation payload, the backend attempts to honor it and store the Type with that specific UUID. A UUID that is already in use, by another Type definition or by any other registry element, produces ``409 Conflict``, following the registry-wide UUID ownership rule. If the client omits the ``id``, the backend automatically generates a new random UUID for the Type.
- **``PUT /types/{type-name}`` semantics (idempotent creation):** ``PUT`` consumes only ``application/json`` and is accepted for the creation of a new Type with the provided identifier. The body must be a complete Type definition whose ``name`` exactly matches the path. To strictly adhere to HTTP ``PUT`` idempotency, if a client submits a definition for an existing Type, the server compares the complete category-specific structural representation — including the discriminator, changelog, supported property declarations and all their constraints, Resource relationship declarations, and Relation endpoints — while excluding only the identity/audit fields ``id`` and ``metadata`` and the server-assigned ``locked`` flag. A Type definition is a closed representation: a member it does not define returns ``400 Bad Request`` (the removed ``notnull`` constraint included; a submitted ``metadata`` or ``locked`` is the only exception, dropped without warning because the server assigns them), as do a cardinality with a negative ``min`` or ``max``, ``max: 0``, or ``min`` greater than ``max``, and a property ``min`` or ``max`` that is not a number, or a decimal one on a property whose ``propertyType`` is neither ``Double`` nor ``Float``. ``ResourceSchema`` has no ``properties`` member; supplying one is an invalid representation and returns ``400 Bad Request``. An identical definition returns ``200 OK`` (no-op); any structural difference returns ``409 Conflict``, because online Type updates are forbidden. A submitted ``id`` is a consistency assertion: when it differs from the persisted UUID of the Type, the request returns ``409 Conflict``. A Type marked ``locked`` is served read-only: any submission that would change it returns ``409 Conflict``. A new definition also returns ``409 Conflict`` when it violates the current Type graph, including unresolved or final parents, unresolved referenced Types, an inheritance cycle, inconsistent multiple inheritance, or contradictory inherited cardinalities. Malformed JSON, an invalid Type representation (simultaneous ``abstract: true`` and ``final: true`` included, since the definition contradicts itself whatever the registry holds), path/body name mismatch, or a Type name that does not match ``^[A-Z][a-zA-Z0-9]*$`` returns ``400 Bad Request``. Both successful outcomes return the persisted Type representation and identify its canonical name-addressed URI through ``Content-Location``. A newly created Type is immediately readable, and its instances carry the corresponding ``supertypes`` linearization. *(Contrast with Query Template definitions: while a Query Template is updated online by submitting a higher ``version``, Types are structural graph contracts and strictly forbid online updates).*
- **Type Evolution & Versioning:** Type update via REST is intentionally not exposed (Type changes are breaking). Type changes (e.g. making a field mandatory) can break running instances and dependent systems. Internally, the framework's update logic inherently blocks Type modification if there are existing instances of that Type. To evolve a Type while keeping the **same Type name**, a framework administrator must perform an offline assessment to evaluate the impact and define a formal data migration plan before manually forcing the upgrade offline.
- **Absence of a standalone ``List`` endpoint:** There is intentionally no standalone ``List`` endpoint (e.g., ``GET /types``) for schema definitions. Clients are expected to discover available Types hierarchically by invoking the ``Read`` endpoint on any known Type and using the ``depth`` parameter. For example, a client can retrieve the entire **Resource** schema tree by calling ``GET /types/Resource?depth=-1``, or dynamically explore a specific sub-branch by calling ``GET /types/Service?depth=-1`` or any other known subtype.
- **Type read semantics:** ``GET /types/{type-name}`` accepts only the exact case-sensitive Type name and always returns a JSON array with the requested Type first. ``depth=0`` returns only that Type, a positive ``depth=N`` includes descendants through exactly N inheritance levels, and ``depth=-1`` includes all descendants; invalid values return ``400 Bad Request``. Multiple-inheritance paths do not duplicate a descendant. ``metadataPolicy=none|root|all`` controls metadata across the flat result (``root`` and ``all`` are equivalent for this endpoint, as all returned types are peer definitions; only ``none`` omits it), and non-global roles receive masked metadata identities. Successful reads expose the canonical name-addressed URI through ``Content-Location`` and, through ``Link: rel=supertype``, each declared parent that is served as a Type definition (see [HATEOAS Navigation](appendix/08-hateoas-navigation.md)). A read never returns a partial subtree.
- **Execute JSON Query (QBE):** ``QUERY /types`` requires a JSON object targeting one of ``ResourceSchema``, ``FacetSchema``, ``IsRelatedToSchema``, ``ConsistsOfSchema``, or ``PropertySchema``; every other category is rejected with ``400 Bad Request`` before database execution, even when its filters would match no rows. The ``depth`` parameter controls descendant expansion for plain-string ``name``, ``source``, and ``target`` matches: ``0`` is exact, a positive value includes descendants through that many levels, and ``-1`` (default) is unbounded. Full Type results honor ``metadataPolicy`` and redact ``metadata.createdBy`` and ``metadata.lastUpdateBy`` for non-privileged roles; Relation Type definitions expose ``source`` and ``target`` as canonical Type-name strings. An ``_emit`` query instead returns only its flat aliases while preserving each selected value's native JSON type, ignores an explicitly supplied ``metadataPolicy`` with ``X-Warning``, and still redacts aliases sourced from sensitive metadata fields. Pagination applies to both full and projected rows; invalid query parameter values return ``400 Bad Request`` (see [Parameter Value Handling](11-api-query-parameters.md#parameter-value-handling)).
- **Type Deletion Constraints:** ``DELETE /types/{type-name}`` is a protected schema operation. Query parameters are unsupported, ignored, and reported through ``X-Warning``. The server MUST reject the request with ``409 Conflict`` when deleting the Type would leave the Type dictionary structurally inconsistent. A Type is deletable through the public REST API only when all the following conditions are true:
  - it has no concrete instances;
  - it has no subtypes;
  - it is not referenced by any other Type definition;
  - it is not referenced in the ``template`` of any Query Template (the Query Template is deleted or updated first), so that no stored Query Template ever names a Type that no longer exists; a Type is referenced when its name is the value of a ``type`` key anywhere in the ``template``, either written literally or as the ``defaultValue`` of the template variable placed there, while an ``_emit`` alias is not a reference;
  - it is not part of the Information System model itself (``locked``).
```

  Reverse-reference checks are evaluated by Type category:
  - Deleting a **Resource** Type is rejected if that Type is used as the source **Resource** Type of any **ConsistsOf** Type definition.
  - Deleting a **Resource** Type is rejected if that Type is used as the source **Resource** Type or target **Resource** Type of any **IsRelatedTo** Type definition.
  - Deleting a **Resource** Type is rejected if that Type is used as the source or the target **Resource** Type in any **Resource** -- **IsRelatedTo** --> **Resource** schema constraint declared by another **Resource** Type.
  - Deleting a **Facet** Type is rejected if that Type is used as the target **Facet** Type in any **Resource** -- **ConsistsOf** --> **Facet** schema constraint.
  - Deleting an **IsRelatedTo** Type is rejected if that Type is used as the relation Type in any **Resource** -- **IsRelatedTo** --> **Resource** schema constraint.
  - Deleting a **ConsistsOf** Type is rejected if that Type is used as the relation Type in any **Resource** -- **ConsistsOf** --> **Facet** schema constraint.
  - Deleting a **`Property`** Type is rejected if another Type definition still uses it as a direct or collection-valued complex `propertyType`.

  In **Resource** Type definitions, every schema constraint has the **Resource** Type that owns the definition as one of its endpoints: as its source or, for an inbound **IsRelatedTo** declaration, as its target. Deleting that owning **Resource** Type does not leave its own constraints dangling, because the whole Type definition is being removed. The deletion is still rejected if the **Resource** Type has instances, has subtypes, is used by a **IsRelatedTo** or **ConsistsOf** Type definition, or is referenced by another **Resource** Type definition.
