# Resource Registry — REST API Foundations & Roles

## HTTP Protocol Foundations

The Resource Registry is designed to embody the principles of the REST architectural style[^fielding-2000], specifically applying them through the lens of Resource-Oriented Architecture (ROA)[^richardson-2007]. It adheres to core RESTful constraints (Client-Server separation, Stateless execution, Layered System topology, and a Uniform Interface based on resource identification, manipulation through representation, and self-descriptive messages). However, to maximize operational efficiency and client developer experience, it embraces specific pragmatic deviations from pure REST (such as client-side URI construction alongside standard RFC 8288 hypermedia link headers), which are explicitly documented throughout this chapter.

Instance-level cacheability (`Cache-Control` / `ETag`) is planned as a non-breaking addition in future iterations.

The service utilizes standard HTTP methods to perform listings of collections and CRUD operations on `HTTP resources`. The following table summarizes the generic HTTP contract mapped across all endpoints.

### HTTP Methods, Safety, and Idempotency

- **Safe Methods:** Do not produce side effects ([RFC 9110 Section 9.2.1](https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.1)).
- **Idempotent Methods:** Repeating the request has the same intended effect as making a single request ([RFC 9110 Section 9.2.2](https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2)).

### Normative Method Intent for Mutations (`PUT` vs `PATCH`)

For this API, the intended semantics are:
- **`PUT` = replace intent** on the addressed instance representation.
- **`PATCH` = merge intent** (partial modification).

This contract follows standard HTTP semantics and JSON Merge Patch conventions:
- HTTP Semantics ([RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110))
- HTTP PATCH Method ([RFC 5789](https://datatracker.ietf.org/doc/html/rfc5789))
- JSON Merge Patch media type ([RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396))

**Pragmatic API note:** This table represents the general baseline rules for the API. Specific exceptions, tailored behaviors, and custom status codes (e.g., endpoints returning Operation Envelopes or safe `POST` operations) are documented in detail within the specific capability tables below. Always refer to the specific endpoint contract for normative behavior.

| Operation | HTTP Method | URL Convention | Success HTTP Status | Safe | Idempotent* |
|-----------|-------------|----------------------------------------|---------------------|:----:|:----------:|
| List | `GET` | `/{collection}` | `200 OK` | Y | Y |
| Query | `QUERY` | `/{collection}` | `200 OK` | Y | Y |
| Exists | `HEAD` | `/{collection}/{instance-id}` | `200 OK`** | Y | Y |
| Create | `POST` | `/{collection}` | `201 Created` | N | N |
| Read | `GET` | `/{collection}/{instance-id}` | `200 OK` | Y | Y |
| Update/Create | `PUT` | `/{collection}/{instance-id}` | `200 OK` / `201 Created` | N | Y |
| Patch | `PATCH` | `/{collection}/{instance-id}` | `200 OK` | N | Y |
| Delete | `DELETE` | `/{collection}/{instance-id}` | `204 No Content` / `200 OK` | N | Y*** |

**\*** *Idempotency vs Identical Responses:* A common misunderstanding in REST architectures is that an idempotent request must return the exact same response body every time. This is false. According to [RFC 9110 Section 9.2.2](https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2), idempotency applies exclusively to the *intended effect on the server state*, not the response payload. For example, a `PUT` operation that triggers an avalanche effect (like `Add to Context`) might return a payload of newly added instances `[R1, R2]` on the first call, and an empty payload `[]` on the second call (because all instances are already in the target Context). Since the final state of the graph on the server is identical in both cases, the operation is formally idempotent, fully honoring the HTTP semantics.

**\*\*** *HEAD and 204 No Content Misconception:* It is a common misconception that because a `HEAD` request does not return a body, it should return a `204 No Content` status. However, per [RFC 9110 Section 9.3.2](https://datatracker.ietf.org/doc/html/rfc9110#section-9.3.2), the `HEAD` method must be identical to `GET` except for the omission of the message body. Therefore, it must return the exact same status code (i.e., `200 OK`) and the exact same headers (including `Content-Length`, `Content-Type`, and `X-Warning`) that the corresponding `GET` request would return. Returning `204 No Content` for a successful `HEAD` when the `GET` returns a `200 OK` JSON body would be a formal violation of the HTTP standard.

**\*\*\*** *Effect-Idempotent Deletions:* In line with [RFC 9110 Section 9.2.2](https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2), repeated deletion requests are idempotent in terms of **final `HTTP resource` state**[^allamaraju-2010]. The first successful deletion may return `204 No Content` (or `200 OK` for endpoints that return an Operation Envelope); subsequent identical calls may return `404 Not Found` if the target is already absent, `204 No Content` again for a Context (which stays in the cemetery after deletion), or `200 OK` with an empty Operation Envelope for an instance that still exists in other Contexts. Client SDKs and retry middleware should classify both success codes (`200`/`204`) and `404` as acceptable terminal outcomes for repeated delete attempts against the same URI.

#### The `QUERY` HTTP Method (RFC 10008) and `POST` Fallback

The API leverages the new HTTP `QUERY` method (proposed standard [RFC 10008](https://www.rfc-editor.org/info/rfc10008)) for endpoints that perform complex, read-only searches requiring a JSON payload (e.g., `QUERY /instances`). This method provides the correct semantics: it is **safe**, **idempotent**, and **cacheable**, avoiding the anti-pattern of using `POST` for pure read operations.

**Network Intermediary Fallback:**
Because some network intermediaries (WAFs, strict corporate proxies, or older load balancers) and legacy HTTP client libraries may reject or drop requests using the `QUERY` method (often returning `405 Method Not Allowed` or `501 Not Implemented`), the API guarantees a robust fallback mechanism. Clients can seamlessly fallback to a standard `POST` request on the `/query`-suffixed path, carrying the `X-HTTP-Method-Override: QUERY` HTTP header (see the rule below). 

The server will transparently intercept the `POST` request, override the method based on the header, and route it to the exact same safe query logic. Client SDKs should be configured to attempt `QUERY` natively, and upon encountering a `405` or `501` network error, automatically retry using the `POST {path}/query` form with the override header (subsequent requests in the same session can then "stick" to the fallback to avoid the latency penalty of repeated failures).

The fallback follows one rule for every `QUERY` endpoint: the native request `QUERY {path}` corresponds to `POST {path}/query` with the `X-HTTP-Method-Override: QUERY` header. The `/query` suffix keeps the fallback distinct from any `POST` on the same path (e.g. instance creation, `POST /instances/{type}`).

| Native request | `POST` fallback (with `X-HTTP-Method-Override: QUERY`) |
|----------------|--------------------------------------------------------|
| `QUERY /contexts` | `POST /contexts/query` |
| `QUERY /types` | `POST /types/query` |
| `QUERY /instances` | `POST /instances/query` |
| `QUERY /query-templates` | `POST /query-templates/query` |
| `QUERY /query-templates/{name}` | `POST /query-templates/{name}/query` |
| `QUERY /admin/raw-query` | `POST /admin/raw-query/query` |

The same rule applies under `/access`. A `POST` without the suffix is not a fallback form: when it carries the `X-HTTP-Method-Override: QUERY` header, it is rejected with `400 Bad Request`, so that it can never be executed as a creation on a path that also accepts `POST`. Conversely, a `POST {path}/query` without the header, or with the header carrying any value other than `QUERY`, is not a query either and returns `400 Bad Request`.

### Omission of Standard Validation Errors (400) and List Authorization (403)

In the endpoint-specific HTTP Contract tables throughout this chapter, generic framework-level errors (such as returning `400 Bad Request` because a client passed a string `?limit=abc` instead of an integer) are intentionally omitted. Only errors dictated by specific business-logic rules (e.g., topology or schema validation) are listed.

Furthermore, once baseline endpoint authorization has succeeded, **base collection `List` operations** never return `403 Forbidden` merely because no data is visible within the caller's Context scope. A caller with no Resource Registry role still receives `403` before collection evaluation, as defined by the role model. For an authorized caller, the system applies visibility filtering at the database query level and returns exactly the set of **`HTTP resource`**s the token allows them to see, which may be an empty array `[]`; absence of visible rows is not itself an authorization failure.

### URL Convention

The presented URLs use the following convention:
- `{collection}` is the base path representing the target domain (e.g., `contexts`, `types`, `instances`, `query-templates`).
- `{instance-id}` is a unique identifier enabling univocal identification of the instance within the collection.

### HTTP Request Headers

- **Authentication Credentials**: All requests *must* include valid authentication credentials. The exact header used (e.g., `Authorization: Bearer <TOKEN>`) depends on the specific API Gateway or middleware securing the deployment. These credentials authenticate the caller and natively bind the request to a specific Context (tenant), determining the baseline authorization scope for the operation (see the "Roles" section below). For details on the specific authentication mechanisms and headers used in the D4Science deployment, see the [Architectural Explanations](12-is-architectural-decisions.md) documentation.
- **`Accept`**: Used for content negotiation ([RFC 9110 Section 12.5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-12.5.1)). Clients should specify `Accept: application/json` to indicate they expect a JSON-formatted response.
- **`Content-Type`**: Required for all requests carrying a payload body.
  - For `POST`, `PUT`, and `QUERY` endpoints with a body, it must be set to `Content-Type: application/json` ([RFC 9110 Section 8.3](https://datatracker.ietf.org/doc/html/rfc9110#section-8.3)). The only exception is `System Restore`, which takes the `application/zip` archive produced by `System Dump`.
  - For all `PATCH` endpoints in this API, the required media type is **`Content-Type: application/merge-patch+json`** ([RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396)).
- **`X-HTTP-Method-Override`**: A widely recognized convention (originally popularized by Google Data APIs and adopted by many frameworks like Express, Spring, and Jersey) used exclusively as a fallback mechanism for network intermediaries that block the `QUERY` method. When a client encounters a block (e.g., `405 Method Not Allowed`), it can transparently convert `QUERY {path}` into `POST {path}/query` carrying this header with the value `QUERY`. The API will process it exactly as if the native `QUERY` method was used.
- **`Prefer`**: Allows clients to request specific server behaviors ([RFC 7240](https://datatracker.ietf.org/doc/html/rfc7240)). Some APIs use this header (e.g., `Prefer: return=minimal|representation`) to allow clients to explicitly request whether the response body should be a highly compact or a detailed structural representation.
- **`X-Request-ID`**: An optional (but recommended) correlation ID passed by the client. The backend service and any intermediate API Gateway use this ID for logging and tracing across the infrastructure to correlate distributed microservice calls, providing robust auditability and debugging capabilities. *(In the D4Science deployment, the SmartGears API Gateway natively manages and propagates this header for distributed tracing. See [Authentication Ecosystem Abstraction](12-is-architectural-decisions.html#authentication-ecosystem-abstraction-d4science-implementation)).*

### HTTP Response Headers

- **`Content-Type`**: `application/json` for successful reads and mutations returning a body. The only exception is `System Dump`, a maintenance export that returns `application/zip`.
- **`Content-Location`**: The canonical URI of the returned payload, or the specific URI that generated a failure in error responses. Like every link target, it stays on the surface the request entered through: under `/access` for a request received through the facade. Applies to creation/update responses (`POST`/`PUT`/`PATCH`), single-instance reads (`Read` on Contexts, Types, Instances, Query Templates), and all error responses. It does **not** apply to `List`/`Query` responses, which return a collection rather than the representation of one URI. For a Context resolved through the `@current` alias, the header reports the resolved Context's own canonical UUID-based URI, never the alias path the client actually requested. *(Note: Because this header applies universally across all resources within this scope, it is omitted from the individual API endpoint tables throughout the documentation for brevity).*
- **`Link`**: Provides HATEOAS (Hypermedia as the Engine of Application State) navigation controls directly at the protocol level ([RFC 8288](https://datatracker.ietf.org/doc/html/rfc8288)). This safely decouples hypermedia links from the core domain JSON payload. *(Note: Because the emitted relations heavily depend on the specific endpoint and its pagination state, see [HATEOAS Navigation Controls](appendix/08-hateoas-navigation.md) for the exact matrix of supported relations per endpoint).*
- **`Preference-Applied`**: Returned when the server has successfully honored a `Prefer` request header ([RFC 7240 Section 3](https://datatracker.ietf.org/doc/html/rfc7240#section-3)). For Resource Registry operation-envelope endpoints, the header is emitted on every successful response to make the applied payload shape explicit, including the default `return=representation` shape when the request omits `Prefer`.
- **`X-Warning`**: Custom HTTP response header used to communicate additional warnings when processing is intentionally lenient (inspired by the deprecated `X-Warning` header from RFC 7234). In this API, it is used to signal unrecognized fields that were ignored during payload processing (see *Forgiving APIs* below).
- **`X-Request-ID`**: The API Gateway returns the correlation ID (either the one provided by the client or an auto-generated one) to allow the client to track the request lifecycle.
- **`X-Result-Scope`**: Returned when the response is scope-filtered or scope-expanded relative to the base URI Context (e.g., hierarchical lookup, state-filtered descendants), or when the token path was resolved through a previous path (`previous-path`, together with `X-Resolved-Context-Path`). It is the normative machine-readable indicator that a successful payload was expanded or filtered relative to what the request addressed. Its absence means that the response is exactly the requested scope, which is not necessarily global: for example, a `contexts` map built with the default `membershipScope=subtree` emits no token. *(Note: Because the returned scope modifiers depend on the specific query and role, see [X-Result-Scope Conventions](appendix/09-result-scope-conventions.md) for the exact matrix of supported values).*
- **`X-Resolved-Context-Path`**: The current path of the operational Context when the caller's token path matched one of that Context's `previousPaths` entries. It is emitted only together with the `previous-path` value in `X-Result-Scope` and is absent for a direct current-path match.

*(Architectural Note on Multi-Value Headers: When an endpoint needs to return multiple values for an HTTP header like `Link` or `X-Warning`, the backend will emit multiple distinct header lines. However, per RFC 9110, intermediate HTTP proxies (like Nginx or HAProxy) are authorized to fold multiple identical headers into a single comma-separated string before delivering the response to the client. Client libraries must be prepared to handle both formats robustly).*

### Status Codes

The service returns standard HTTP status codes:
- **Success Codes:** 
  - [`200 OK`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.1): Request successful.
  - [`201 Created`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.2): Target `HTTP resource` created successfully.
  - [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.3.5): Operation successful, no data returned.
- **Client Error Codes:** 
  - [`400 Bad Request`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.1): Invalid request format or parameters. This includes general malformed syntax, any malformed or unknown query parameter value (e.g., `?limit=abc`, `?hierarchical=yes`; see [Parameter Value Handling](11-api-query-parameters.html#parameter-value-handling)), and domain-specific validation failures.
  - [`401 Unauthorized`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.2): Missing or invalid authentication token. *(Note: This applies globally to all endpoints and is therefore omitted from the individual API endpoint tables below for brevity).*
  - [`403 Forbidden`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.4): Insufficient permissions for the operation (or operations attempted in a Context with an invalid `effectiveState`).
  - [`404 Not Found`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.5): Requested `HTTP resource` does not exist.
  - [`405 Method Not Allowed`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.6): The used HTTP method is not supported for the requested URL.
  - [`409 Conflict`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.10): Conflict with the current state of the target `HTTP resource` (e.g., topological integrity rules or schema violations).
  - [`415 Unsupported Media Type`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.16): The request body has a `Content-Type` the endpoint does not accept (e.g., a body that is not `application/json` on a `PUT`, `POST`, or `QUERY`, not `application/merge-patch+json` on a `PATCH`, or not `application/zip` on `System Restore`). *(Like `401`, it applies to every endpoint that takes a body and is omitted from the individual endpoint tables.)*
- **Server Error Codes:** 
  - [`500 Internal Server Error`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.1): Unexpected server error.
  - [`503 Service Unavailable`](https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.4): Service temporarily unavailable.

### Error Catalogue & JSON Payload

When the API encounters a failure (and returns a `4xx` or `5xx` status code), it provides a structured JSON error payload. Unlike verbose standard error wrappers (e.g., Spring Boot default errors), this API adopts a minimalistic approach.

**Canonical Error Schema:**
```json
{
  "message": "An EService instance must be described by ConsistsOf -> StateFacet with the following constraint: max:1, min:1. Found 0 instances. The constraint has been defined by EService type."
}
```
- `message`: A detailed, human-readable (and machine-actionable) description of the specific failure (e.g., schema validation details, missing source dependencies, role restrictions). It reports the first violation found, not every one: validation stops at the first failure.

**Contextual Information in HTTP Headers:**
To strictly separate the protocol layer from the business payload, the API relies on HTTP headers rather than duplicating context in the JSON body:
- **Status Code:** The HTTP status code is exclusively conveyed via the standard HTTP response line.
- **Path/URI:** The URI that generated the error is conveyed via the standard `Content-Location` HTTP header (or inherently known by the client as the request URI).

**Disambiguation Rules:**
- **`400 Bad Request` vs `409 Conflict` on Mutations:** 
  - `400` is returned for malformed payloads, invalid JSON, or structural request errors (e.g., passing invalid query parameters or structurally invalid enum combinations like an illegal `propagationConstraint`). 
  - `409` is returned when the payload is perfectly valid JSON but violates the business or graph logic against the current state of the infrastructure. This includes:
    - failing schema cardinality validation like `min/max`.
    - attempting to attach a **Facet** instance via multiple **ConsistsOf** instances (Facet Exclusivity violation).
    - attempting to link the same two **Resource** instances via redundant polymorphic **IsRelatedTo** instances.
    - attempting to add a **Relation** instance when its source **Resource** instance is missing from the Context.
    - attempting to surgically remove from a Context (or delete globally) the last remaining **Facet** instance (or its **ConsistsOf** instance) of a **Resource** instance in that Context (which violates the strict presence rule).
    - optimistic concurrency failures.
- **`403 Forbidden` vs `404 Not Found` on Lookups:** 
  - `404` means that the exact lookup key addressed by the request does not exist. For point-instance endpoints, that key is `(type, instance-uuid)`: a UUID stored under a different concrete Type therefore still returns `404`.
  - `403` means that the exact lookup key exists, but it is outside the authorization or Context scope requested by the caller. This includes an instance that exists only in a descendant when the point lookup omits `hierarchical=true`. *(Note: This distinction is intentionally preserved on the `/access` facade for operational diagnosability, relying on UUID entropy to mitigate existence-probing risks).*
- **`403 Forbidden` vs `400 Bad Request` on Role Checks:** The role is evaluated first: a caller whose role does not allow the operation receives `403 Forbidden` before the path identifiers, the query parameters, or the body are examined, so a malformed UUID or body from that caller still returns `403 Forbidden`.

### Context-Agnostic System Fields

The `lastUpdateTime` of a **Resource** instance is computed globally (across all Contexts) rather than being dynamically filtered to the caller's Context. While this means a user might see an updated timestamp caused by a mutation in an out-of-scope Context, it is an intentional architectural choice. It does not leak any sensitive data (similarly to the `403 Forbidden` vs `404 Not Found` existence logic). Instead, it can be exploited as a generic signal by a privileged user to perform an eventual hierarchical read—either downwards through the Context itself if it has child Contexts, or upwards through parent Contexts—to investigate the global mutation. *(Note on Future Compatibility: Since optimistic concurrency control via `ETag` is planned for future iterations, this global timestamp logic will ensure that conditional updates remain coherent and deterministic across the entire federated graph).*

### Idempotency in Structural Mutations (`PUT`)

Standard REST semantics dictate that `PUT` operations must be idempotent: repeating the request multiple times must yield the exact same graph state as executing it once ([RFC 9110 Section 9.2.2](https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2)).

Because the IS-Model allows clients to submit deep structural payloads (e.g., a **Resource** instance containing nested **ConsistsOf** instances pointing to new **Facet** instances), the API achieves idempotency by requiring the client to explicitly supply the `id` for every instance in a `PUT` payload — the root instance and every instance nested in it alike (the **ConsistsOf** and **Facet** instances of a **Resource** instance, and the new target **Facet** or **Resource** instance of a Relation created on its own), whether it already exists or is being created for the first time. Any omitted `id` in a `PUT` payload is strictly rejected with `400 Bad Request`, and so is an omitted `propagationConstraint` on any Relation instance in it. An `id` that does not yet exist creates a new instance; an existing `id` of the same concrete Type and consistent topology updates it in place (see [`PUT` Create-or-Update and UUID Reuse](09-api-instances-and-admin.md) for the `409` cases).

This makes the operation natively idempotent: repeating the exact same payload for any reason — including a client retry after a lost response — targets the exact same IDs and converges on the same graph state, rather than duplicating nested elements. See [Architectural Explanations: Mandatory Client-Supplied UUIDs for Structural Idempotency](12-is-architectural-decisions.html#rest-api-mandatory-client-supplied-uuids-for-structural-idempotency) for the rationale behind this design choice.

## REST API Design Principles

Before diving into the specific roles and endpoints, it is important to clarify the architectural rationale that drives the API behavior. The Resource Registry prioritizes ecosystem resilience, developer experience, and structural flexibility over strict, academic REST purism.

1. **Ecosystem Resilience (Postel's Law) and Forward Compatibility:** Following *Postel's Law*[^postel-1981] (the Robustness Principle: *"be conservative in what you do, be liberal in what you accept from others"*), the REST API uses graceful degradation for harmless unknown or inapplicable input. Unrecognized query parameters, read-only payload fields, and parameters that have no meaning for the targeted representation are ignored and reported through `X-Warning` where specified. A recognized parameter that explicitly requests additional authority or scope is different: if the caller lacks the required role, the request is rejected with `403 Forbidden` and is never silently executed with narrower semantics. This applies, for example, to privileged uses of `hierarchical`, `propagationMode=surgical`, `membershipScope=global`, and `bypassHierarchyCheck`. Likewise, a recognized parameter with a malformed or unknown value is rejected with `400 Bad Request` rather than replaced by a default (see [Parameter Value Handling](11-api-query-parameters.html#parameter-value-handling)).
2. **Open Content Model (Mixed Schema):** The underlying Information System is designed to be intrinsically extensible via a "Mixed Schema" paradigm, allowing dynamic injection of arbitrary fields beyond the core type definitions. Enforcing strict payload validation by default would create an architectural bottleneck, contradicting the open nature of the graph database and severely limiting the ecosystem's ability to evolve and accommodate dynamic telemetry or metadata injection from unpredictable external sources.
3. **Reducing Client Complexity (Developer Experience):** A strict validation approach creates a tightly-coupled, hostile developer experience. For instance, the `metadata` block is structurally read-only. In a strict system, a client fetching an `HTTP resource` via `GET` would be forced to explicitly scrub the `metadata` block before sending the payload back via `PUT` or `PATCH`. Failure to do so would trigger validation errors. Furthermore, if a concurrent background process updates the metadata while the client is reading it, a strict validation on the returned read-only block would unnecessarily reject the client's subsequent business data update. By adopting a forgiving approach, we free developers from implementing brittle, boilerplate payload-scrubbing logic. *(Note: Optimistic concurrency control via `ETag` and `Last-Modified` validation ([RFC 7232](https://datatracker.ietf.org/doc/html/rfc7232)) is recognized as the correct architectural pattern for preventing concurrent update overwrites and is planned for future iterations).*

   **Normative `X-Warning` schemas:**
   While the API is forgiving and processes payloads containing extraneous data or query parameters, it is built as a "well-behaved" architectural component that transparently signals what it chose to ignore or modify via standard HTTP `X-Warning` headers. This is a **header-only** signal to preserve pure, ROA-style resource representations[^richardson-2007] without injecting warning envelopes into the JSON body.
   - Header name: `X-Warning`
   - Warn code: `199` (miscellaneous warning)
   - Warn agent: `resource-registry`
   
   To protect the HTTP infrastructure (API Gateways, WAFs) from header-size-limit crashes, the server restricts the volume of headers. Multiple warnings are emitted as multiple distinct `X-Warning` header lines (intermediaries may fold them, see the note on multi-value headers above).

   - **Ignored Fields (Max 10 fields per instance):** A single warning entry is produced for each distinct identifiable instance that contained ignored fields. *(Note: If ignored fields occur inside a nested, unidentifiable **`Property`** object, the warning is attributed to the parent instance that owns the property, and the field is identified using dot-notation, e.g., `myProperty.subField`).* The entry enumerates up to a maximum of 10 ignored fields per instance (sorted lexicographically). If more are found, the `ignored-fields` string is truncated with an ellipsis token (e.g., `...<TRUNCATED>`), while the true count is preserved. These entries then participate in the single global emission algorithm below. When ignored fields are the only warning family and overflow occurs, this yields the first 9 instance-specific entries followed by the global summary; mixed warning families are always selected by the global round-robin rule. *(Note on Header Encoding: To guarantee the structural integrity of the `X-Warning` header, any ignored field name that violates the strict alphanumeric naming constraints and contains non-ASCII characters, spaces, or header delimiters (like `,`, `;`, `=`, or `"`) is strictly **Percent-Encoded** (URL-encoded) before being appended to the `ignored-fields` list. For example, a dropped invalid field named `price€` will appear as `price%E2%82%AC` and `my;field` as `my%3Bfield`).*
     - Format: `type=<ElementType>; id=<ElementUUID>; ignored-fields=<field1,field2,...<TRUNCATED>>; ignored-count=<integer>`
   
   - **Ignored Query Parameters:** Emitted when a query parameter is unknown, inapplicable to the operation, or overridden by another directive such as a `Prefer` header. A recognized privilege-expanding parameter is never ignored for lack of privileges; it is rejected with `403 Forbidden`.
     - Format: `ignored-query-parameters=<comma-separated-parameters>; ignored-count=<integer>; reason=<reason-string>`
     - Encoding Rule: each parameter name in `ignored-query-parameters` is percent-encoded when it contains non-ASCII characters, spaces, or header delimiters (e.g., `,`, `;`, `=`, `"`). Clients should decode explicitly after parsing the warning token.
   
   - **Modified Query Parameters:** Emitted when a valid query parameter value is clamped to a server bound (e.g., `limit` above the maximum). A malformed or unknown value is never modified; it returns `400 Bad Request`.
     - Format: `modified-query-parameters=<comma-separated-parameters>; requested-value=<value>; applied-value=<value>; reason=<reason-string>`

   **Global X-Warning Emission Algorithm (Anti-DoS):**
   The three warning schemas above define the payload format of individual warning entries. Header-volume protection is a separate, global algorithm that applies to the full warning stream, regardless of warning family.
   - **Global cap:** At most 10 `X-Warning` headers per response.
   - **No overflow:** If total warnings <= 10, emit all warnings.
   - **Overflow:** If total warnings > 10:
     - Reserve the last slot for one global summary warning.
     - Emit 9 detail warnings selected with a fair round-robin across families (`Ignored Fields`, `Ignored Query Parameters`, `Modified Query Parameters`) to avoid bias caused by raw emission order.
     - Preserve original in-family order for determinism.
   - **Global Summary Warn-Text Format:**
     - `omitted-warnings-total=<integer>; omitted-ignored-fields-warnings=<integer>; omitted-ignored-fields-total=<integer>; omitted-ignored-query-parameter-warnings=<integer>; omitted-modified-query-parameter-warnings=<integer>`
   - **Summary Key Presence Rule:**
     - All summary keys are always emitted.
     - Missing families are represented with `0` (keys are not omitted).
   - **Global Summary Full Header Example:**
     - `X-Warning: 199 resource-registry "omitted-warnings-total=27; omitted-ignored-fields-warnings=8; omitted-ignored-fields-total=142; omitted-ignored-query-parameter-warnings=19; omitted-modified-query-parameter-warnings=0"`

   **Examples:**
   - `X-Warning: 199 resource-registry "type=EService; id=1a50a46d-2210-4a17-bd2f-22450b109264; ignored-fields=name; ignored-count=1"`
   - `X-Warning: 199 resource-registry "ignored-query-parameters=syncExistingHierarchy; ignored-count=1; reason=Not applicable to POST creation"`
  - `X-Warning: 199 resource-registry "ignored-query-parameters=metadataPolicy; ignored-count=1; reason=Overridden by Prefer: return=minimal"`
   - `X-Warning: 199 resource-registry "modified-query-parameters=limit; requested-value=-1; applied-value=50; reason=Clamped to system maximum"`

   *Clarification on System Fields & Schema-Mixed Mode:* 
   - **System Fields (Immutable):** Fields that are purely system-managed (`type`, `supertypes`, `id`, `metadata`, `contexts`) are strictly immutable by the client. Attempting to send `supertypes`, `metadata`, or `contexts` (or, for a Context, `path`, `effectiveState`, and `children`) in a mutation payload results in them being explicitly dropped, whatever their value, to reduce client boilerplate: these are server-computed views whose content depends on the caller's role, parameters, and time of reading, so the server never reads them from the client. This system-field rule takes precedence over category allowlists, and because the drop is expected it **does not** trigger an `X-Warning`. Identity validation is operation-specific: the body `type` and `id` of a URL-addressed `PUT` or `PATCH` target must match the path, and every nested instance in a `PUT` must carry the client-supplied identity required for structural idempotency. By contrast, `POST` creation may omit `id` at every payload level; warning generation never turns that optional creation identifier into a request requirement. *(Note on Type Registration: The system field names are reserved and cannot name a custom property in a Type Schema definition; the complete list is in [Property Naming Constraints](05-data-representation-contexts-schemas.html#property-naming-constraints-normative)).*
   - **System Fields (Mutable):** The `propagationConstraint` is also a system-defined structural field, but unlike the others, the client **can** define and mutate its values. However, it is still strictly subject to the schema rules of the specific Relation (e.g., the `remove` constraint for a **ConsistsOf** instance is forced to `cascade` and cannot be changed to `keep`). *(Note on Type Registration: Just like `metadata`, `propagationConstraint` is a formal specification property of the base **Relation** schema and cannot be redefined by any **Relation** subtype. While nothing technically prevents a user from naming a custom data field `propagationConstraint` within a **Facet** schema, doing so is an anti-pattern: the field will be persisted as standard custom data, but the system will never use it to evaluate avalanche behavior).*
   - **Payload Contracts by Public Base Type:** The five public extensible base Types have distinct content models and category-specific fields:
     - **Resource:** Closed data schema. In addition to the common system fields, a Resource payload accepts the structural `consistsOf` field. It does not support custom property fields; any other direct field is dropped and triggers an `X-Warning`.
     - **Facet:** Schema-mixed data payload. A Facet accepts declared and arbitrary custom property fields but no category-specific topological field.
     - **IsRelatedTo:** Schema-mixed Relation payload. It accepts declared and arbitrary custom property fields, the immutable `source` reference, the immutable `target` (a reference, or the complete representation of the target; see [Relation `PUT`](09-api-instances-and-admin.md)), and the mutable `propagationConstraint`.
     - **ConsistsOf:** Schema-mixed Relation payload. It accepts declared and arbitrary custom property fields, the immutable `source` reference, the immutable `target` (a reference, or the complete representation of the target; see [Relation `PUT`](09-api-instances-and-admin.md)), and the mutable `propagationConstraint`.
     - **`Property`:** Schema-mixed nested value. It accepts declared and arbitrary custom property fields but has no independent graph identity and cannot be addressed as a standalone instance. A nested **`Property`** value that carries `id` or `metadata` returns `400 Bad Request`: both are managed by the system on an instance and no client can set them anywhere, so a value that carries them is malformed, not a representation with fields to drop. This is why **`Metadata`** is not a **`Property`** Type that a definition could place in any field: a client could otherwise expect the system to maintain it there as it does on an instance.
   - **Schema-Mixed Field Constraints:** In the four schema-mixed categories above, an unknown valid custom field is persisted rather than ignored. The names `in` and `out`, and any name that starts with `in_` or `out_`, are reserved: such a field is dropped and reported exactly as a name that violates the naming rule is. "Arbitrary field" refers exclusively to basic field types (e.g., String, Integer, Boolean), plain JSON arrays/objects, or explicitly defined **`Property`** schema subtypes. A custom property field cannot natively embed structural graph elements or system objects such as a **Facet** instance, a **Relation** instance, or a **Context**. The `"type"` keyword is globally reserved for polymorphic deserialization (e.g., resolving a concrete **`Property`** subtype) and cannot be used as a custom field name, even inside generic schema-mixed objects. A nested JSON object whose `"type"` attempts to instantiate a core topological element (e.g., `"type": "Context"`) or names an unknown Type (e.g., `"type": "Vehicle"`) is rejected with `400 Bad Request`. *(**Opaque Blob Exemption:** A field formally defined by its Type schema as `JsonObject` or `JsonArray` is a closed opaque boundary; any nested `"type"` key is plain data and has no polymorphic or topological meaning.)* A schema-mixed field produces an `X-Warning` only when it is actively rejected and dropped: a custom field whose name does not follow the regex in [Property Naming Constraints](05-data-representation-contexts-schemas.html#property-naming-constraints-normative), for example because it starts with the reserved characters `@` or `_`, is dropped and reported in the `Ignored Fields` family; it is not rejected with `400 Bad Request`. The naming rule applies to the fields of the instance and of its nested **`Property`** values; inside a plain JSON object that carries no `type`, keys are data and are stored as submitted. This tolerance applies to mutation payloads only: in a JSON query, an `_`-prefixed key that is not a known operator is a syntax error and returns `400 Bad Request` (see [JSON Query Syntax Reference](appendix/07-json-query-syntax-reference.md)).
   
   *Implementation Note on Edge Cases:* The `id` reported in the warning is the actual UUID of the instance. For a `POST` payload that omits the root or a nested instance ID, the backend uses the newly generated UUID in the warning message, which the client will also find in the response body. If processing fails before an ID can be supplied or generated, the warning may use `id=null`.

   *Note: Lenient processing is an interoperability mechanism, not a bypass of data integrity. Validation remains strictly enforced on the declared fields and their expected types. For example, if the schema dictates a field must be an Integer and the client provides a String, the backend will predictably reject the payload with a `400 Bad Request`.*

4. **Type Stability and Global Privacy Compliance:** The architecture is not only designed for technical coherence but also to satisfy stringent legal requirements regarding data privacy that are now practically global. Privacy laws heavily restrict access to personal identifiers, meaning that sensitive metadata fields (like `createdBy` or `lastUpdateBy`) can only be viewed by privileged users who hold the explicit legal responsibility to do so. To enforce this data minimization without breaking strictly-typed programmatic clients (e.g., Java/Jackson), the REST API enforces *Type Stability*. When an unprivileged client requests an instance, these fields are not omitted, nor are they set to `null`. Instead, they are obfuscated using the explicit sentinel string `"[REDACTED]"`. Removing the field or returning `null` would force every client in the ecosystem to implement defensive parsing and null-checks, risking unpredictable `NullPointerException`s and application crashes when privacy policies dynamically filter data. The choice of `"[REDACTED]"` over `null` is a well-established industry standard for REST APIs handling sensitive data (widely adopted by tech giants like GitHub, Stripe, Discord, and AWS for log masking and payload sanitization). Preserving the structural integrity of the string field ensures robust and deterministic client behavior across all security contexts, flawlessly marrying legal compliance with software stability.
   - *Sensitive Metadata Query Semantics:* Queries operate on the caller-visible logical representation produced by privacy authorization, never on a hidden stored value. This privacy view exists before and independently of response-shaping controls such as `metadataPolicy`, `_emit`, or a future projection mechanism; omitting a field from the response does not change predicate semantics. For `IS-M`, `Infra-M`, and `IS-Admin`, predicates involving `metadata.createdBy` or `metadata.lastUpdateBy` therefore compare the stored identity. For every other role, those fields participate in predicates and projections, and therefore in result counts and pagination, only as the visible sentinel `"[REDACTED]"`. A predicate containing a real identity consequently does not match a redacted value, while a predicate containing `"[REDACTED]"` may match it. Merely referencing a sensitive field never causes `403 Forbidden`; authorization failures remain reserved for an inaccessible endpoint, scope, operation, or parameter.
   - *Sensitivity Is Contract-Defined, Not Name-Inferred:* Automatic `"[REDACTED]"` masking applies only to system fields that this reference explicitly classifies as sensitive. In particular, the `who` field of a system-produced Context History `Event` is sensitive, while an application-defined custom field named `who` has no special treatment and may intentionally contain public provenance. Field names alone never trigger masking.
   - *Disclaimer on User-Defined PII:* The Information System cannot proactively police or identify PII (Personally Identifiable Information) maliciously or accidentally injected by users into arbitrary custom property fields (e.g., inside an `Encrypted` property or a generic string field). The responsibility for classifying, sanitizing, and validating domain-specific custom data remains strictly with the publishing applications.
5. **Graceful Degradation on Authorization Scope:** Endpoints remain callable for every role and return the result bounded by the caller's scope, instead of a hard authorization error: instances in non-`active` descendant Contexts are omitted for roles without the administrative state override, and the omission is signaled through `X-Result-Scope: state-filtered`. A role never receives a silently narrowed answer to an explicit request for more: a privilege-expanding parameter it is not entitled to returns `403 Forbidden`. Similarly, `List Instance Contexts` remains available to every role: by default it returns the memberships in the token Context and its descendants, and the complete set only when an authorized role requests `membershipScope=global`. This allows clients to implement generic operational logic without scattering complex, role-based conditional requests throughout the frontend code.

## Roles

### Authentication & Identity Context

At the core service level, the Resource Registry is completely agnostic to specific authentication protocols (e.g., OIDC, SAML, api-keys). It operates strictly on an **Identity Context**. The service expects an upstream API Gateway or middleware filter to have already validated the incoming request and to supply the verified identity (username) and their evaluated roles for the target operation.

**The API Contract:**
From the client's perspective, the API requires the caller to provide valid authentication credentials (e.g., via the `Authorization` header, such as a Bearer token) that the deployment's API Gateway understands. 

The underlying system expects distinct scopes of authorization to be reflected in the Identity Context provided by the Gateway. The architecture strictly separates **Visibility Scope** (the ability to read and query) from **Mutation Scope** (the ability to write, update, or delete). Because IS data is generally discoverable, all Context-bound roles possess Subtree Visibility:

- **Universal Roles:** Trusted Information System and infrastructure roles (`IS-Manager`, `Infrastructure-Manager`, and `IS-Admin`) whose capabilities are recognized independently of Context-tree jurisdiction.
- **Subtree Roles (Infrastructure Administration & Publishing):** Roles (`Context-Manager`, `IS-Editor`) that grant Visibility and Mutation Scope starting from a specific target Context and propagating downwards to all its descendants.
- **Local Writer Roles:** Roles (e.g., `IS-Contributor`) that grant Subtree Visibility (allowing hierarchical reads across descendants) but strictly **Local** Mutation Scope (writes are confined to the exact token Context, with no downward propagation).
- **Local Reader Roles:** Roles (e.g., `IS-Member`) that grant Subtree Visibility (allowing hierarchical reads across descendants) but possess **No** Mutation Scope (all write operations are forbidden).

**Context Switching (Token scope):**
Because operations are inherently bound to Contexts, the client must ensure that the credentials provided for an API request authorize them for the specific Context they intend to operate on. **It is crucial to understand that an authorization token is NEVER "global"; it is ALWAYS cryptographically bound to one specific Context.** What makes an administrator "global" is the *role* they hold (e.g., `IS-Manager`), not the token itself. Even a global administrator must present a token bound to a specific Context to execute an operation. If a user navigates between different Contexts, their client application must provide the appropriate scoped credentials (e.g., requesting a new Context-scoped token from the Identity Provider) before executing operations within the new boundary.

### Understanding Hierarchical Scope

```eval_rst
*(Note: Throughout this chapter, you will encounter terms like Virtual Organization (VO) and Virtual Research Environment (VRE) used to represent parent and child Contexts. These refer to the 3-level hierarchy used in the D4Science infrastructure (Root* :math:`\rightarrow` *VO* :math:`\rightarrow` *VRE) and are used strictly as pragmatic examples to aid comprehension. The Resource Registry natively supports an arbitrary N-level Context hierarchy).*
```

Before detailing the specific roles, it is important to clarify how hierarchical operations work across the Resource Registry API:

- **Hierarchical Visibility (Read Only):** By using the `hierarchical=true` query parameter on read operations, an authorized user holding a token for a parent Context can see all instances located across its descendant Contexts. This provides a complete picture of the entire subtree in a single request, without the need to query every single child Context individually, and without forcing instances to be explicitly added to the parent Context.
- **Hierarchical Sync (Write Operations):** While CRUD operations generally target the specific Context identified by the authorization token, roles with subtree mutation scope can explicitly pass `hierarchical=true` on creation and update to synchronize the payload into the token Context and all its descendants, or `syncExistingHierarchy=true` to synchronize it only into the descendants where the instance is already present (see [`hierarchical`](11-api-query-parameters.html#hierarchical)).

### IS-Manager and Infrastructure-Manager

*Abbreviation used in tables: **IS-M / Infra-M***

These two roles grant **identical, unrestricted permissions** within the Resource Registry. They differ only in the authority they express outside it:

- **IS-Manager** — administrative role scoped to the Information System service specifically.
- **Infrastructure-Manager** — supreme administrative role covering the entire infrastructure and therefore also the complete Information System.

| Privilege / Scope | Description |
|----------|-------|
| Role type | Universal Administrative Role |
| Scope | Universal — the system assumes this role grants authority across every Context |
| Token Constraint | While authority is universally recognized, for strictly Context-scoped endpoints (e.g., managing instances within a specific Context), the client must still provide valid credentials addressing that target operational Context. |
| Can manage Contexts | Yes — full (any Context in the infrastructure) |
| Can manage Types | Yes — full (create, read, delete) |
| Can manage Instances | Yes — full, including cross-Context operations (an Add to Context outside the token Context subtree and its siblings requires `bypassHierarchyCheck=true`) |
| Can manage Query Templates | Yes — full (create, update, read, run, delete) |
| Metadata Visibility | Full (no fields obfuscated) |

> **Universal Authority vs Operational Context:** The system considers these roles to be universally authoritative. However, when an endpoint requires an explicit operational Context by API contract (e.g., executing a context-bound creation), the client must still provide credentials that declare the intended operational Context.

**Semantics:** The most privileged administrative roles. Holders can perform every operation, within the token rules that each operation defines: Context lifecycle operations require the token of the parent (or of the closest common ancestor for a reparenting), and an Add to Context in an unrelated branch requires the explicit `bypassHierarchyCheck=true`.

### IS-Admin

`IS-Admin` is the trusted functional administrator of Information System data. It sits below
`IS-Manager` but above the operational writer roles, and its authority is not derived from a
position in the Context tree.

| Privilege / Scope | Description |
|----------|-------|
| Role type | Universal Information System Administrative Role |
| Scope | Universal for the capabilities granted to the role |
| Token Constraint | A Context-bound token still selects the source or operational Context when an endpoint needs one; the role itself is not limited to that Context's subtree. |
| Can manage Contexts | No — Context lifecycle belongs to `IS-Manager`, `Infrastructure-Manager`, and `Context-Manager` |
| Can manage Types | No — read-only access |
| Can manage Instances | Yes — including explicit cross-tree sharing, surgical repair, and hierarchical Add to Context |
| Can manage Query Templates | Yes — create, update, read, run, and delete |
| Metadata Visibility | Full (no fields obfuscated) |

**Cross-tree guardrail:** A standard Add to Context may target the token Context's subtree or a
sibling Context. A target in any other branch requires the caller to state the intent explicitly
with `bypassHierarchyCheck=true`. The flag is an operational safeguard, not an additional grant of
authority.

### Context-Manager

*Abbreviation used in tables: **Ctx-M***

`Context-Manager` is an infrastructure role scoped to a managed Context and its descendants. It is
not an Information System super-role and is deliberately independent from `IS-Admin`.

| Privilege / Scope | Description |
|----------|-------|
| Role type | Subtree Infrastructure Administrative Role |
| Scope | Subtree — scoped to the specified Context and all its descendants |
| Token Constraint | Credentials must address the operational Context required by the specific operation. |
| Can manage Contexts | Partial — manage child/descendant Contexts only (cannot modify the managed Context itself) |
| Can manage Types | No — read-only access |
| Can manage Instances | Yes — create, update, delete, and share within the managed subtree |
| Can manage Query Templates | Partial — list/read global definitions; execute using instance-query scope and `effectiveState` rules; cannot create/update/delete |
| Metadata Visibility | Filtered — `createdBy` and `lastUpdateBy` obfuscated (`"[REDACTED]"`) |

For instance management, the token selects the Context in which the operation starts. For Context
lifecycle operations, the token must address the immediate parent of the target Context, or, for a
reparenting, the closest common ancestor of the current and the new parent. The role
never authorizes a sibling or unrelated branch merely because the caller manages another branch.

### IS-Editor

*Abbreviation used in tables: **IS-Editor***

| Privilege / Scope | Description |
|----------|-------|
| Role type | Subtree Writer Role |
| Scope | Subtree (similar to Context-Manager, but narrower) |
| Token Constraint | **Credentials must address the operational Context required by the specific operation.** |
| Can manage Contexts | No — read-only access to existing Contexts |
| Can manage Types | No — read-only access |
| Can manage Instances | Yes — create/update/delete/share within the Context hierarchy below the managed Context (includes hierarchical creation and update; not hierarchical Add to Context) |
| Can manage Query Templates | Partial — list/read global definitions; execute using instance-query scope and `effectiveState` rules; cannot create/update/delete |
| Metadata Visibility | Filtered — `createdBy` and `lastUpdateBy` obfuscated (`"[REDACTED]"`) |

**Semantics:** A subtree writer role. The holder acts within a specific subtree rooted at their *managed Context*. They can create, update, delete, and share instances within their Context and its descendants (including hierarchical creation and update, but not hierarchical Add to Context), but cannot manage Context lifecycle, Query Template definitions, or cross-tree membership. Crucially, they can **only operate on `active` Contexts**.

**Hierarchical Business Rule:** Holding the `IS-Editor` role for a Context grants the authority to perform downstream operations (like `Add to Context` or `hierarchical=true` writes) into its descendant Contexts. However, because they lack administrative override privileges, an `Add to Context` into a **non-`active`** descendant Context (e.g., `suspended`) is rejected with a `403 Forbidden`, while a `hierarchical=true` or `syncExistingHierarchy=true` write skips the **non-`active`** descendant Contexts.

### IS-Contributor

*Abbreviation used in tables: **IS-Contrib***

| Privilege / Scope | Description |
|----------|-------|
| Role type | Local Writer Role |
| Scope | Context-bound — scoped strictly to the requested Context |
| Token Constraint | **Credentials must address the operational Context required by the specific operation.** |
| Can manage Contexts | No — read-only access to existing Contexts |
| Can manage Types | No — read-only access |
| Can manage Instances | Yes — create/update/delete strictly within the current Context (no cross-context sharing or hierarchical writes) |
| Can manage Query Templates | Partial — list/read global definitions; execute using instance-query scope and `effectiveState` rules; cannot create/update/delete |
| Metadata Visibility | Filtered — `createdBy` and `lastUpdateBy` obfuscated (`"[REDACTED]"`) |

**Semantics:** A purely local writer role. The holder acts strictly within their current token's *managed Context*. They possess Subtree Visibility (can read hierarchically), but strictly **Local Mutation**. They can create, update, and delete instances within their Context, but they cannot perform hierarchical write operations (`PUT ?hierarchical=true`), and they cannot share instances to other Contexts (`Add to Context`). They can **only operate on `active` Contexts**.

### IS-Member

*Abbreviation used in tables: **IS-Member***

| Privilege / Scope | Description |
|----------|-------|
| Role type | Local Reader Role — assigned explicitly |
| Scope | Context-bound — scoped to the authorized Context |
| Token constraint | Credentials must be valid and address the target Context |
| Can manage Contexts | No — read-only access to existing Contexts |
| Can manage Types | No — read-only access |
| Can manage Instances | No — read-only access to existing instances |
| Can manage Query Templates | Partial — list/read global definitions; execute using instance-query scope and `effectiveState` rules; cannot create/update/delete |
| Metadata Visibility | Filtered — `createdBy` and `lastUpdateBy` obfuscated (`"[REDACTED]"`) |

**Semantics:** The baseline read-only role. Like every other Resource Registry role, it must be assigned explicitly: a caller whose token carries no Resource Registry role for the Context (e.g. only the generic `Member` role) is refused with `403 Forbidden`, read operations included. This role grants Subtree Visibility (allowing hierarchical reads across descendants), but it is strictly **Read-Only** (List, Read, Exists, and Queries). All write or destructive operations are forbidden.

## API Reference

The following legend applies to all API tables:

| Symbol | Meaning |
|--------|---------|
| ✅ | Action available for the role. The endpoint is callable for that role, even if some query-parameter values are restricted. |
| ⚠️ | Action conditionally available. Extra operational constraints apply (for example: parent-token requirement, managed-subtree boundaries, or instance/topology-specific constraints). |
| ❌ | Action not available for the role (returns `403 Forbidden`). |
| — | Not applicable |

For detailed endpoint specifications and parameter matrices, refer to:
- [API Contexts & Types Reference](08-api-contexts-and-types.md)
- [API Instances & Admin Reference](09-api-instances-and-admin.md)
- [API Query Templates & Access Reference](10-api-query-templates-and-access.md)
- [API Query Parameters](11-api-query-parameters.md)

## Architectural Choices

### Ecosystem Boundaries & Generalization Intent

The architectural choices described in this chapter are profoundly shaped by the ecosystem in which the Resource Registry operates: the **gCube Framework** and the **D4Science Infrastructure**. 
While the underlying data model and the service itself were originally conceived as a highly generalized Information System for Research Infrastructures and industry (*Frosini, L., 2019*)[^frosini-2019], the current implementation must satisfy the strict operational constraints of D4Science, which funds and hosts it.

Consequently, while we strive for absolute RESTful purity, some design choices represent pragmatic compromises:
- **Network-Level Segregation:** The decision to expose only the `/access` branch to the external internet while keeping write operations behind a VPN is a specific D4Science security topology.
- **Gateway Abstraction:** Currently, the service relies on an upstream API Gateway to supply the verified identity and roles. The service core is entirely decoupled from the actual token format (e.g., JWT) or the underlying Identity Provider. For deployment specifics within D4Science, refer to the [Architectural Explanations](12-is-architectural-decisions.md) documentation.

This chapter transparently acknowledges these boundaries, defending the following practical decisions without hiding the intent to remain as generalized as possible.

### Preemptive Architectural Explanations

To preempt common doubts for users approaching this API from a strict REST background, the following design choices have been intentionally made:

- **Sharing via `PUT` vs `POST` (Idempotent Links):** The operation to add an instance into a new Context (`PUT /instances/{type}/{instance-uuid}/contexts/{new-context-uuid}`) triggers a complex "avalanche effect" that calculates dependencies and adds mandatory **Facet** instances to ensure data consistency. Some purists might argue this complex business logic warrants an RPC-style `POST` endpoint (e.g., `/add`). However, the operation is purposefully exposed as a `PUT` on the explicit *membership URI* because the outcome is strictly **idempotent**. Asserting the intent "this instance must belong to this Context" multiple times results in the exact same final graph state. Using `PUT` perfectly honors the HTTP/1.1 idempotency guarantees without falling back to ambiguous verb-in-path RPC patterns. Because this operation always returns the resulting graph state via the Operation Envelope, a successful request always returns `200 OK`. The returned envelope will contain the affected instances if the graph topology was modified, or an empty list when nothing had to be added. With `surgical` or `shallow`, an explicitly targeted instance that is already present in the target Context ends the operation at once, with an empty list; `deep` continues from it and may still add missing downstream instances.

- **The `Context-Manager` Jurisdiction:** A `Context-Manager` of Context `/A` cannot modify or delete `/A` itself, but only its contents and descendants. A division manager has authority within the division but cannot rename or close the division itself; that decision belongs to its parent administrator. `IS-Admin` is deliberately not an alias for this infrastructure role: it administers Information System data across Context boundaries but does not manage Context lifecycle.
- **Context-Bound Tokens vs. Multi-Context Tokens:** The API requires clients to obtain and use a specific token bound to the target Context (rather than a single, massive token containing all user roles across all Contexts). While this requires "token switching" for users navigating the hierarchy, it is a necessary architectural compromise within the gCube ecosystem for three critical reasons:
  1. **HTTP Header Limitations:** In production, a top-level Context-Manager might have access to hundreds of sub-Contexts. A single token encapsulating all these roles would exceed standard HTTP header size limits, resulting in truncation by most generic networking equipment (e.g., HAProxy).
  2. **Infrastructure Standardization:** The Resource Registry operates behind SmartGears, which acts as an API Gateway and handles token verification automatically. Accepting a single "catch-all" token to manually verify parent-child jurisdiction would require disabling standard SmartGears verification, breaking the uniform security model of the infrastructure.
  3. **Operation Ambiguity:** A Context-bound token inherently declares the target scope of the request. A global multi-Context token would force every API endpoint to require an explicit extra parameter to disambiguate the target Context.
- **The `/admin` Segregation for Whole-System and Destructive Operations:** The API uses an explicit `/admin` path prefix for whole-system maintenance, diagnostics, and destructive operations to create a hard network-level barrier. For the full architectural rationale, see the [Architectural Explanations](12-is-architectural-decisions.html#the-admin-segregation-for-whole-system-and-destructive-operations).
- **The `DELETE` vs `/admin/` `DELETE` Paradigm (Reference Counting):** A common critique in highly multi-tenant environments is the semantic ambiguity of a "delete" operation. If a user executes a `DELETE` from their specific Context on an instance added to 100 Contexts, destroying the instance globally would be catastrophic for the other 99 tenants. To prevent this, the API adopts strict *Reference Counting* semantics for standard users and managers:
  - `DELETE /instances/...`: Functions as a "remove from Context". It removes the instance *only* from the caller's Context (triggering local `remove` propagation constraints). Actual physical destruction (Garbage Collection) occurs automatically only when the instance is removed from its *very last* Context.
  - `DELETE /admin/instances/...`: The `Admin Remove from Context` operation. It scales its destructive power based on the token Context and the `?hierarchical=true` flag. It can perform a surgical hierarchical wipe on a specific subtree, or, if executed from the Root Context, it achieves true global annihilation across all Contexts. Because of its destructive potential, this explicit path is strictly reserved for global administrators (`IS-Manager`, `Infrastructure-Manager`).
- **Transactional Execution & Concurrency Contract (Current Behavior):** Write operations are executed within a single backend transaction per request. The underlying graph persistence layer uses optimistic concurrency control. If two concurrent writes race on overlapping graph elements, one transaction can commit successfully while the conflicting one fails and is surfaced as `409 Conflict` to the caller. The failed request changed nothing and can be repeated as it is; the Java clients report it as a `ConcurrentWriteException`. The API currently does **not** expose entity version fields nor support conditional update headers such as `ETag` / `If-Match`.
  - **Client expectation:** on `409 Conflict`, clients should re-read current state and retry only when business-safe.
  - **Future compatibility note:** if conditional requests are introduced, an instance-level validator can be derived from metadata update timestamps (computed over the effective instance graph view, including composed **Facet** instances / **Relation** instances).
- **Shared **Facet** instance Mutation (Graph Topology vs Context Bound):** Since **Facet** instances are independent instances attached to **Resource** instances via **ConsistsOf** instances, they are shared by reference across Contexts, not duplicated. If a user updates the property field values of a shared **Facet** instance in one Context, that mutation is immediately reflected globally across all other Contexts where the **Facet** instance is present. This is a deliberate design choice and an operational necessity stemming from the native behavior of graph databases, ensuring configurations remain aligned across the ecosystem. Users with `IS-Editor` roles must be aware of this "great power, great responsibility" dynamic when modifying shared instances.

## Known Criticisms & Deliberate Deviations

The API also exhibits behaviors that prioritize pragmatism and generic client compatibility over strict REST rules. These deliberate deviations from purism are acknowledged below. It is worth noting that even Roy T. Fielding, the original author of the REST architectural style, has [acknowledged the dangers of dogmatic adherence](https://doi.org/10.1145/3106237.3121282) [^fielding-2017] (sometimes jokingly referring to overzealous practitioners as "RESTafarians"), advocating instead for a *principled design* where architectural styles are applied pragmatically to solve real-world domain constraints. In this spirit, the Resource Registry API occasionally deviates from pure REST when necessary:

- **Bulk Harvesting vs. Offset Pagination:** Architectural reviews correctly point out that using `limit` and `offset` for massive machine-to-machine data harvesting (e.g., syncing 50,000 instances) in a highly active graph is mathematically unsafe, as concurrent writes cause page boundaries to shift, resulting in duplicated or skipped records. They advocate for Cursor-based pagination. While theoretically correct, this is a deliberate **non-case** for the Resource Registry. The `/access` facade and list endpoints are designed for human-driven UI navigation and targeted operational queries, where offset pagination is perfectly adequate and universally supported by clients. Infrastructure-scale bulk harvesting (like OAI-PMH synchronization or data-warehouse replication) is not intended to be performed by blindly looping over the live transactional REST API with offsets. Thus, the significant backend complexity required to implement globally stable, stateful Cursor-based pagination across partitioned graphs is intentionally avoided.
