# Resource Registry — API Operational Cookbook & Advanced Use Cases

Because the Resource Registry employs a strictly typed graph database under the hood, standard REST operations (`PUT`, `PATCH`, `DELETE`) have specific graph-level side effects. This section details common operational patterns and corner cases.

## The Shared Instance Mutation (Global Effect for Facets and Relations)

**Scenario:** An instance (e.g., a **Facet** instance or an **IsRelatedTo** instance) is shared across multiple Contexts (e.g., Context `C1` and Context `C2`). You want to update a field (e.g., a specific property of the **Facet** instance, or the `propagationConstraint` of the **Relation** instance), and you *want* that update to be reflected for everyone.
**Action:** Send a `PUT` or `PATCH` containing the modified payload, **explicitly including its existing `id`**.
**Graph Mechanism:** The backend resolves the existing instance using the provided ID and updates its property fields. Because fields are intrinsically bound to the single underlying instance, the change is immediately visible globally across all Contexts where that instance is shared. (This applies equally to `Encrypted` fields and structural system fields like `propagationConstraint`).

## Context-Local Instance Forking (Copy-on-Write)

**Scenario:** A **Resource** instance is shared across Contexts `C1` and `C2`. You want to update a **Facet** instance *only for your Context* (`C1`), branching off from the shared configuration without affecting Context `C2`.
**Action:** Send a `PUT` payload where the modified **ConsistsOf** instance **and** its nested **Facet** instance each carry a **freshly client-generated `id`** (a UUID that does not yet exist in the system), instead of the `id` of the shared instances.
**Graph Mechanism:** The backend interprets each not-yet-existing `id` as a request to create a new instance. Specifically, within the operational Context `C1`, it explicitly drops the existing **ConsistsOf** instance (and consequently its target **Facet** instance) from the Context. It then creates a brand new **Facet** instance and attaches it to the **Resource** instance via a newly created **ConsistsOf** instance, using the client-supplied `id` values. Because Contexts are strictly isolated, the original **ConsistsOf** and **Facet** instances remain completely untouched and safely attached to the shared **Resource** instance in Context `C2`.
*(Both ids must be fresh — reusing the existing **ConsistsOf** instance's `id` while only generating a new one for the **Facet** instance would retarget a shared **ConsistsOf** instance to a different **Facet** across Contexts, which is topologically invalid; see [`09-api-instances-and-admin.md`, "Single Context Update (Local Copy-on-Write)"](../09-api-instances-and-admin.html#single-context-update-local-copy-on-write) for the full explanation).*

## The Cardinality Chicken-and-Egg (`min: 1, max: 1`)

**Scenario:** A **Resource** instance has a mandatory **Facet** instance (e.g., `StateFacet` with `min:1, max:1`). You need to completely replace this **Facet** instance with a new one for your Context. You cannot `POST` the new one first (violates `max: 1`) and you cannot `DELETE` the old one first (violates `min: 1`).
**Action:** Send a `PUT` request for the entire **Resource** instance, removing the old **Facet** instance from the payload array and including the new one with a freshly client-generated `id` (a UUID that does not yet exist in the system).
**Graph Mechanism:** The `PUT` operation acts as a "Full Replace" for the Context. The backend calculates the delta: it removes the omitted old **Facet** instance and attaches the new one. Validation constraints (like `min/max`) are strictly evaluated **at the end of the transaction**. Since the final state contains exactly 1 `StateFacet`, validation passes seamlessly.

## Hierarchical Downstream Mutations (`hierarchical` vs `syncExistingHierarchy`)

When executing a `PUT` operation on a **Resource** instance as a role with subtree mutation scope (`IS-Editor` or higher), you have two powerful mechanisms to push topological changes (e.g., changes to its attached **Facet** instances) down a Context tree. It is crucial to understand the difference to avoid unintended data proliferation.

*(Note on Parameter Conflict: The flags `hierarchical=true` and `syncExistingHierarchy=true` describe two mutually exclusive propagation strategies. If a client mistakenly passes BOTH flags on the same `PUT` request, the API strictly rejects the request with a `400 Bad Request` to prevent ambiguity).*

### The Surgical Sync (`?syncExistingHierarchy=true`)

**Scenario:** You want to push a configuration change down to all child VREs (Contexts `C_Child_1`, `C_Child_2`) *only* if they have already explicitly "opted-in" by having the **Resource** instance shared with them.
**Action:** Issue a `PUT /instances/{resource-type}/{instance-uuid}?syncExistingHierarchy=true` targeting the parent Context.
**Graph Mechanism:** This acts as a safe, cascading sync. The backend applies the `PUT` state replacement to the **Resource** instance in the parent Context AND in all descendant Contexts **where the **Resource** instance already exists**. It does *not* implicitly share the **Resource** instance to child Contexts that did not already have it.

### The Blanket Replication (`?hierarchical=true`)

**Scenario:** You want to enforce a global baseline, ensuring a specific **Resource** instance is present and updated across *all* descendant Contexts, overriding any local absences.
**Action:** Issue a `PUT /instances/{resource-type}/{instance-uuid}?hierarchical=true` targeting the parent Context.
**Graph Mechanism:** The backend applies the submitted **Resource** payload as a full replacement in the parent Context and every descendant Context within the authorization scope. A child that did not previously contain the **Resource** receives exactly the Resource topology described by the payload. This is payload synchronization, not an `Add to Context` avalanche: **IsRelatedTo** instances and other live topology absent from the payload do not follow.

## Surgical **Relation** instance / **Entity** instance Deletion (`?propagationMode=surgical`)

**Scenario:** You accidentally created an **IsRelatedTo** instance with `remove: cascade` to an important target **Resource** instance, and you need to remove the **Relation** instance without triggering the avalanche that would remove the target **Resource** instance too.
**Action:** Issue a `DELETE /instances/{type}/{instance-uuid}?propagationMode=surgical` (where `{instance-uuid}` is the UUID of the **Relation** instance or the source **Entity** instance).
**Graph Mechanism:** This triggers a surgical non-cascading `Remove from Current Context`. The backend removes the targeted **Relation** instance (or the targeted **Entity** instance and its strictly required **Facet** instances) from the token Context, but explicitly **ignores** any `remove` propagation constraints towards external instances. As for every standard `DELETE`, an instance is physically deleted only when it has no remaining Context membership. This acts as a safe "undo" facility, but must be used carefully to avoid leaving detached or unlinked instances floating in the Context unintentionally.

## Descriptive-Topology Closure on Direct Relation Add

When managing graph topologies, a strict theoretical model would dictate that an edge cannot be added to a Context unless both its source and target nodes are already present. However, the IS-Model API implements a deliberate "Developer Experience (DX) bypass" to simplify interactions.

If a client attempts to execute an `Add to Context` directly on a **Relation** instance, and the target **Entity** instance is missing from the destination Context, the system does not fail with a dependency error. Instead, it **implicitly pulls** the target **Entity** into the Context, actively bypassing the `unpropagate` constraint if present on the **Relation**.

**Design Rationale:**
This behavior is an intentional system compromise. It allows clients to share a specific connection (and its necessary downstream requirement) in a single API call, rather than forcing them to first discover the target ID, make a separate `PUT` call to add the target, and finally make a `PUT` call for the relation. While it creates a minor asymmetry with the theoretical propagation model (where `unpropagate` means "stop"), the necessity of preserving structural integrity (no dangling edges) combined with developer ergonomics justifies this implicit closure.

*(Note: The implicit pull strictly applies to the target. If the **source Resource** is missing, the operation strictly fails with a `409 Conflict`. This enforces the model's unbreakable rule: **the avalanche is strictly downstream**. We only force the downstream pull to preserve topological closure when bypassing `unpropagate`. If a client wants to add the source, they must explicitly invoke `Add to Context` on the source instance itself. We never implicitly pull upstream. Furthermore, this implicit downstream pull triggers standard propagation rules for the target itself. If the pulled target **Resource** instance has mandatory **Facet** instances governed by `add: unpropagate` constraints, those **Facet** instances will correctly NOT be pulled. Consequently, the target **Resource** instance will fail its final schema validation, and the entire transaction will be predictably rejected with a `409 Conflict`. The system behaves as a strict deterministic engine: it does not magically override downstream constraints to rescue a poorly modeled resource instance).*

## Canonical Mutation Semantics: `PUT` (Replace Intent) vs `PATCH` (Merge Intent)

To reduce ambiguity for client implementers, this section uses the **same initial state** and shows the resulting state after each method. *(Note: This is a simplified structural example to demonstrate the delta mechanics; a real `EService` instance would require additional mandatory facets to pass strict schema validation).*

- **`PUT`** = replace intent on the addressed instance representation.
- **`PATCH`** = merge intent (partial modification).

**Shared initial state (conceptual):**

```json
{
  "type": "EService",
  "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "id": "8bb0a45e-1110-4b17-ad3f-11450b109277",
      "target": {
        "type": "StateFacet",
        "id": "7aa991ba-2043-4259-8d85-b6377ac778df",
        "value": "created"
      }
    },
    {
      "type": "ConsistsOf",
      "id": "9cc0b56f-2220-4c28-be4f-22560c200388",
      "target": {
        "type": "AccessPointFacet",
        "id": "39bd3a5b-9737-40bc-8934-6e491aee8fed",
        "endpoint": "https://old.example.org"
      }
    }
  ]
}
```

### `PUT` Replace Intent (example)

**Request headers:**
- `Content-Type: application/json`
- `Accept: application/json`

**Client request (`PUT /instances/EService/1a50a46d-2210-4a17-bd2f-22450b109264`):**
```json
{
  "type": "EService",
  "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "id": "c11d1e44-5555-4a17-bc4e-33660a209999",
      "propagationConstraint": {
        "type": "PropagationConstraint",
        "add": "propagate",
        "remove": "cascade"
      },
      "target": {
        "type": "StateFacet",
        "id": "d22e2f55-6666-4b28-cd5f-44770b30aaaa",
        "value": "active"
      }
    }
  ]
}
```
*(Both `id` values are freshly client-generated UUIDs that don't yet exist in the system — this is a new **ConsistsOf**/**StateFacet** pair, not an update of the omitted **AccessPointFacet** one.)*

**Final state after `PUT` (intent):**

```json
{
  "type": "EService",
  "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "id": "c11d1e44-5555-4a17-bc4e-33660a209999",
      "target": {
        "type": "StateFacet",
        "id": "d22e2f55-6666-4b28-cd5f-44770b30aaaa",
        "value": "active"
      }
    }
  ]
}
```

- The addressed instance representation is replaced by what was sent.
- Omitted parts (e.g., the **AccessPointFacet** instance) are not preserved by intent.
- Validation (cardinality/schema constraints) is evaluated on the final post-mutation state.

### `PATCH` Merge Intent (example)

**Request headers:**
- `Content-Type: application/merge-patch+json`
- `Accept: application/json`

**Client request (`PATCH /instances/EService/1a50a46d-2210-4a17-bd2f-22450b109264`):**
```json
{
  "type": "EService",
  "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "id": "8bb0a45e-1110-4b17-ad3f-11450b109277",
      "target": {
        "type": "StateFacet",
        "id": "7aa991ba-2043-4259-8d85-b6377ac778df",
        "value": "active"
      }
    }
  ]
}
```

**Final state after `PATCH` (intent):**

```json
{
  "type": "EService",
  "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
  "consistsOf": [
    {
      "type": "ConsistsOf",
      "id": "8bb0a45e-1110-4b17-ad3f-11450b109277",
      "target": {
        "type": "StateFacet",
        "id": "7aa991ba-2043-4259-8d85-b6377ac778df",
        "value": "active"
      }
    },
    {
      "type": "ConsistsOf",
      "id": "9cc0b56f-2220-4c28-be4f-22560c200388",
      "target": {
        "type": "AccessPointFacet",
        "id": "39bd3a5b-9737-40bc-8934-6e491aee8fed",
        "endpoint": "https://old.example.org"
      }
    }
  ]
}
```

- Only the provided property field value (for `StateFacet.value`) is merged/updated.
- Unspecified property fields remain unchanged.

> **Media type rule:** In this API, PATCH requests use `application/merge-patch+json`.

| Method | Client sends | Server intent on addressed instance representation |
|--------|--------------|----------------------------------------------------|
| `PUT`  | full instance representation | replace with submitted instance representation |
| `PATCH` | partial instance representation (delta) | merge only submitted property field values |

## Moving or Renaming a Context: Previous Paths

A rename or a reparent changes the `path` of a **Context** and of all its descendants, while tokens issued for the old paths stay in circulation. Each outgoing path is kept in the Context's `previousPaths`, so those tokens keep resolving to the same Context. The rules are in [`08-api-contexts-and-types.md`](../08-api-contexts-and-types.md); the examples below show them at work. All `PATCH` requests use `Content-Type: application/merge-patch+json`.

### Reparenting a VRE

**Scenario:** `/gcube/old-vo/VRE1` (id `4a1b2c3d-5e6f-4708-8192-a3b4c5d6e7f8`) must move under `/gcube/new-vo` (id `b1829304-c5d6-4e7f-f8a9-b0c1d2e3f4a5`).
**Action:** A Context whose local `state` is `active` cannot be moved or renamed (`409 Conflict`). Suspend it, move it, then reactivate it:

```
PATCH /contexts/4a1b2c3d-5e6f-4708-8192-a3b4c5d6e7f8
```
```json
{ "state": "suspended" }
```
```
PATCH /contexts/4a1b2c3d-5e6f-4708-8192-a3b4c5d6e7f8
```
```json
{ "parent": { "id": "b1829304-c5d6-4e7f-f8a9-b0c1d2e3f4a5" } }
```

Response to the move — `200 OK`:

```json
{
    "type": "Context",
    "id": "4a1b2c3d-5e6f-4708-8192-a3b4c5d6e7f8",
    "name": "VRE1",
    "path": "/gcube/new-vo/VRE1",
    "previousPaths": [ "/gcube/old-vo/VRE1" ],
    "state": "suspended",
    "effectiveState": "suspended",
    "parent": {
        "type": "Context",
        "id": "b1829304-c5d6-4e7f-f8a9-b0c1d2e3f4a5",
        "name": "new-vo",
        "path": "/gcube/new-vo",
        "state": "active",
        "effectiveState": "active"
    },
    "children": []
}
```

```
PATCH /contexts/4a1b2c3d-5e6f-4708-8192-a3b4c5d6e7f8
```
```json
{ "state": "active" }
```

**Mechanism:** The move writes three events in the Context's history (`GET /contexts/{context-uuid}/history`), all with `how: PATCH`:

| `where` | `what` |
|---------|--------|
| `parent` | `<old-vo id> -> b1829304-c5d6-4e7f-f8a9-b0c1d2e3f4a5` |
| `path` | `/gcube/old-vo/VRE1 -> /gcube/new-vo/VRE1` |
| `previousPaths` | `added /gcube/old-vo/VRE1` |

The suspension and the reactivation write their own `state` events.

### Renaming a VO: the Cascade

**Scenario:** The VO `/gcube/a` (id `7c2e9a41-58d3-4b6f-a0e2-93f1c6d8b4a5`, already `suspended`) has two VREs, `/gcube/a/c` and `/gcube/a/d`. It is renamed to `f`.
**Action:**

```
PATCH /contexts/7c2e9a41-58d3-4b6f-a0e2-93f1c6d8b4a5
```
```json
{ "name": "f" }
```

**Mechanism:** In one transaction the VO becomes `/gcube/f` and its VREs `/gcube/f/c` and `/gcube/f/d`. Each Context keeps its own outgoing path: `/gcube/a`, `/gcube/a/c` and `/gcube/a/d` respectively. The history events are:

| Context | `where` | `what` | `how` |
|---------|---------|--------|-------|
| `f` | `name` | `a -> f` | `PATCH` |
| `f` | `path` | `/gcube/a -> /gcube/f` | `PATCH` |
| `f` | `previousPaths` | `added /gcube/a` | `PATCH` |
| `c` | `path` | `/gcube/a/c -> /gcube/f/c` | `PATCH (cascade from 7c2e9a41-58d3-4b6f-a0e2-93f1c6d8b4a5)` |
| `c` | `previousPaths` | `added /gcube/a/c` | `PATCH (cascade from 7c2e9a41-58d3-4b6f-a0e2-93f1c6d8b4a5)` |

`d` gets the same pair as `c`. A token bound to `/gcube/a/c` now resolves to `c`, reported by `X-Resolved-Context-Path: /gcube/f/c`.

### A Token with an Old Path

**Scenario:** Months after the move, a client whose credentials were never reissued still presents a token for `/gcube/old-vo/VRE1`.
**Action:** `GET /contexts/@current`
**Mechanism:** No Context has that `path` any more; the lookup falls back to `previousPaths` and finds `VRE1`. The response is `200 OK` with the same body as a direct match, plus two headers:

```
X-Result-Scope: previous-path
X-Resolved-Context-Path: /gcube/new-vo/VRE1
```

Retention has no time limit: the fallback works the same after four days or four years. The headers give the caller, and any monitoring watching `X-Result-Scope`, a signal that a stale path is still in use.

### Replacing the Previous Paths

`previousPaths` in a `PATCH` replaces the whole retained set (RFC 7396): the submitted array is exactly what remains, and `null` or `[]` clears it. Each removed and each added value writes its own event (`where: previousPaths`, `what: removed <path>` or `added <path>`, `how: PATCH`).

- **Clearing after the transition.** Once every integration uses the new path, `{ "previousPaths": [] }` removes `/gcube/old-vo/VRE1`. From then on a token still bearing it gets `404 Not Found` on `GET /contexts/@current`.
- **Registering a path by hand.** A pre-migration path that was never recorded, still used by external tokens, can be added: `{ "previousPaths": [ "/gcube/historic-vo/VRE1" ] }`. Remember that the array is the whole set: keep the existing values in it.
- **Replacing and moving together.** In `{ "parent": { "id": "b1829304-c5d6-4e7f-f8a9-b0c1d2e3f4a5" }, "previousPaths": [] }` the replacement is applied first and the move second, so the result holds only the path the move has just left behind.

### Reusing a Path

**Scenario:** A new VO is created with a path that another Context still keeps in its `previousPaths`, e.g. `/gcube/old-vo` after the original VO was renamed.
**Action:**

```
PUT /contexts/c3d4e5f6-7081-4920-a3b4-c5d6e7f8a9b0
Content-Type: application/json
```
```json
{
    "type": "Context",
    "id": "c3d4e5f6-7081-4920-a3b4-c5d6e7f8a9b0",
    "name": "old-vo",
    "parent": { "id": "<gcube id>" }
}
```

**Mechanism:** In the same transaction as the creation, the old value is removed from the other Context's `previousPaths`, with an event on that Context: `where: previousPaths`, `what: removed /gcube/old-vo`, `how: PUT (path reclaimed by c3d4e5f6-7081-4920-a3b4-c5d6e7f8a9b0)`. Resolution was already safe, since a current `path` always wins over a previous one; the removal only keeps the representation honest, so that no Context advertises a path it can no longer receive.
