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

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:

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, 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, 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, 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) 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 documentation.

  • Accept: Used for content negotiation (RFC 9110 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). 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).

  • 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). 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).

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). 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 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). 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 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:

  • Client Error Codes:

    • 400 Bad Request: 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), and domain-specific validation failures.

    • 401 Unauthorized: 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: Insufficient permissions for the operation (or operations attempted in a Context with an invalid effectiveState).

    • 404 Not Found: Requested HTTP resource does not exist.

    • 405 Method Not Allowed: The used HTTP method is not supported for the requested URL.

    • 409 Conflict: Conflict with the current state of the target HTTP resource (e.g., topological integrity rules or schema violations).

    • 415 Unsupported Media Type: 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:

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:

{
  "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).

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 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 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).

  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) 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).

    • 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), 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), 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, 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).

    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 NullPointerExceptions 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

(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 \(\rightarrow\) VO \(\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).

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), a Tracked Update or a Delete being also accepted from a token for the Root Context, 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:

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 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.

  • The Removal 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 /instances/{type}/{instance-uuid}/contexts/{context-uuid}: The Remove from Context operation, the inverse of Add to Context. It removes the instance from the Context named in the URL (@current for the token Context) and, with the explicit ?hierarchical=true flag, from all its descendants. It can remove the instance from a specific subtree or, on a Root Context, from every Context of that tree. hierarchical=true and bypassHierarchyCheck=true are reserved to IS-Manager, Infrastructure-Manager, and IS-Admin, as on Add to Context.

  • 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 [^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.