Resource Registry — Data Representation (Instances)

Instance Representation

System-Generated Fields

When reading instances via the REST API, the service automatically injects operational fields to assist the client.

Order of System Fields

According to the JSON specification, an object is an unordered set of name/value pairs. Therefore, the exact ordering of keys in the REST API payloads is not technically guaranteed by the server, nor is the server expecting clients to submit payloads in a strict order.

However, purely for didactic purposes, all JSON examples presented in this documentation follow a standardized, predictable ordering for the foundational system fields at the top of the object, before declaring any type-specific data fields:

  1. type

  2. supertypes

  3. id

  4. metadata

  5. contexts

It is important to note that the instance examples presented below are as rich as possible to illustrate the full structural representation. However, in standard REST operations, fields like metadata and contexts might not be returned in the payload depending on the client’s request (e.g., via specific query parameters).

type Field

In a federated graph supporting an Open-World Paradigm and dynamic schemas, every instance must explicitly declare “what it is”. The type field serves this primary purpose: it identifies the exact concrete schema that governs the instance. Knowing the exact type enables both the server and the clients to perform proper validation, polymorphic deserialization, and dynamic class instantiation. As detailed below, if a client does not natively recognize the specific type declared (e.g., because a new schema was installed at runtime), it relies on the supertypes array to gracefully fall back to a known abstraction.

supertypes Array

The supertypes array lists the ancestors of the instance’s Type in C3 linearization order (see Type Linearization below), ending with the public base Type (Resource, Facet, IsRelatedTo, ConsistsOf, or Property); the internal primitives beneath the base Types are never listed. It provides a robust polymorphic fallback mechanism for clients parsing the JSON: if a client encounters an instance of a Type that it does not recognize (e.g., a Type dynamically installed in the infrastructure after the client was compiled), the client will not know how to natively instantiate or handle it. By iterating through the supertypes array, the client can fall back to the first ancestor Type it does recognize, ensuring it can still safely deserialize and interact with the instance using a known interface. Because it is returned by the server purely for this fallback reason, the client is not required to send it when performing write operations. (Note: Unlike the extendedTypes array in a Type schema definition—which only declares the direct, first-level inheritance—the supertypes array returned on instances includes every ancestor up to the public base Type).

(Note: This array is not present in the representations of structurally known foundational elements—e.g., Context, Metadata, PropagationConstraint, ConsistsOf, QueryTemplate—because their strict hierarchy is immutable and already known by the system and any client. It is strictly injected for instances belonging to dynamically installed schema Types, such as EService or SoftwareFacet, ensuring that a client unaware of the specific dynamic schema can still understand its lineage. As a concrete corollary: while a direct instance of the base ConsistsOf Type does not possess the array, instances of its dynamically installed subtypes—like the IsIdentifiedBy instance shown in the JSON example below—DO receive the supertypes array).

(Note on Abstract Types: Resource, Facet, and IsRelatedTo do not appear in the list above because they are abstract and cannot be instantiated directly. If they were instantiable, they too would not possess a supertypes array, as they are part of the foundational model).

(Note on Embedded Properties: The supertypes array is typically associated with graph instances possessing an identity (like Resource, Facet, IsRelatedTo, and ConsistsOf instances). However, it is also injected into embedded Property values (e.g., inside an Event property) if those values belong to a polymorphic property type hierarchy. This is strictly required to allow strongly typed clients (like Java Jackson) to correctly deserialize nested polymorphic objects without prior knowledge of the full schema).

Type Linearization (Multiple Inheritance)

Because the IS-Model permits multiple inheritance (a Type schema can define multiple extendedTypes), the order in which a Type inherits from its ancestors must be deterministic to ensure predictable polymorphic fallback mechanisms for clients parsing the supertypes array.

The system enforces a strict C3 Linearization[^barrett-1996] (also known as Method Resolution Order, or MRO) when computing the supertypes array for any graph instance or embedded polymorphic property. When a Type extends multiple parent Types, its linearization is computed by merging its direct parents in the exact order they are declared, followed by the linearizations of those parents. The C3 algorithm guarantees two crucial properties:

  1. Monotonicity: No parent Type will ever appear before its child Type in the resolved ancestor list.

  2. Local Precedence: The explicit order of direct parents declared in the schema is strictly respected.

(Note: The server proactively guarantees this integrity. If a schema designer attempts to register a new Type with an inheritance graph that produces a cycle or an unresolvable C3 conflict, the system rejects the schema registration with 409 Conflict, as for every violation of the current Type graph (see PUT /types/{type-name} semantics). For a pedagogical explanation of the C3 algorithm, refer to the Python 2.3 Method Resolution Order documentation).

id Field (REST Profile Mandate)

(REST Profile Note: While the abstract IS-Model allows for implementation-specific ID formats, the REST API strictly mandates the use of UUIDs for all id fields to guarantee global uniqueness and predictable flat addressability across the federated graph).

Client Parsing Resilience (Open-World Extensibility)

Because the Information System operates under an Open-World Assumption, any Resource instance can dynamically have arbitrary Facet instances attached to it via ConsistsOf instances (beyond what is strictly dictated by its schema). Furthermore, through the Schema-Mixed Mode, any instance (including IsRelatedTo instances) can host arbitrary custom property fields.

Therefore, it is a strict architectural requirement that clients MUST implement resilient JSON parsing capable of preserving unknown data. A client parsing an instance representation must not crash when encountering unrecognized property fields or unrecognized Facet instances attached via ConsistsOf instances. Instead, it must successfully instantiate them (e.g., placing custom properties into an extras map, and generically parsing the attached instances) so that they are preserved. Failing to do so guarantees that the client will eventually crash or accidentally destroy data when re-serializing an instance that another user extended with a new, unforeseen aspect or property.

metadata Field

The metadata field encapsulates provenance and tracking information for the instance (such as creation and modification timestamps and authors). This field is strictly generated and managed by the system. It may be omitted from standard read payloads depending on the client’s request parameters to save bandwidth.

lastUpdateTime Evaluation Logic

For any graph instance, the lastUpdateTime inside the Metadata block represents its latest modification. The specific evaluation logic depends on the instance type:

  • Resource instance: Because it acts as a logical aggregate, its lastUpdateTime is specifically evaluated as the maximum lastUpdateTime among the Resource instance itself, all its ConsistsOf instances, and all its attached Facet instances across all Contexts. (For the architectural and performance rationale regarding this global evaluation, refer to the API Authorization constraints in the REST API chapter).

  • Facet instance: It strictly represents its own individual modification timestamp.

  • IsRelatedTo instance: It strictly represents its own individual modification timestamp.

  • ConsistsOf instance: It strictly represents its own individual modification timestamp.

contexts Map

The contexts field is a read-only map (UUID -> Path) of the Contexts where the instance is present. It is generated by the server on every read: a client cannot set it, and a submitted value is dropped. Each value is the current path of the Context at the time of the read, never null. The map contains the memberships selected by membershipScope: by default those in the token Context and its descendants, and the complete set only when an authorized role requests membershipScope=global. A map obtained with the default scope is therefore not the complete membership of the instance. Crucial operational note for clients: if a Context is renamed or reparented, any Path string a client kept becomes stale. However, the Context UUID (the Key of the map) remains permanently stable and is the only valid identifier for subsequent API operations. A Resource instance exists in a Context if and only if it belongs to that Context’s “Venn diagram” (by possessing at least one Facet instance in that Context). The map is not returned by default and must be requested explicitly through contextsPolicy. (For architectural details on how Contexts and Venn diagrams are physically implemented, see Contexts as Record-Level Security (RLS) Groups).

(Note: The contexts map applies exclusively to Entity and Relation instances. It conceptually makes no sense and is therefore not present on system-level objects (like Context, QueryTemplate, and schema Type definitions) nor on embedded property values (like Metadata and PropagationConstraint)).

Examples: Base Instances

Resource Instance

A full structural representation of a Resource instance is typically serialized as a hierarchical JSON aggregate, nesting the root Resource instance and its attached Facet instances via ConsistsOf instances (or its subtypes) within a single JSON document. (Note: When ConsistsOf instances are nested inside the consistsOf array of a Resource instance payload, the source field is intentionally omitted, as it implicitly defaults to the enclosing Resource instance. When represented as standalone instances, the source field is explicitly included). (Note: The first element in the consistsOf array below uses IsIdentifiedBy. This is a concrete subtype of ConsistsOf in the gcube-model—its presence in the consistsOf array confirms it acts as a composition edge, not an association edge. For references on fields, types and their inheritance, please refer to the live-registry type documentation, generated directly from this instance’s live schema; gcube-model types can also be queried directly (GET /types/{type-name}, QUERY /types). Furthermore, the JSON examples are highly simplified for didactic purposes; for example, the EService instance shown here would not be considered valid in production as it lacks mandatory schema structures).

{
    "type": "EService",
    "supertypes": ["Service", "GCubeResource", "Resource"],
    "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
    "metadata": {
        "createdBy": "luca.frosini",
        "creationTime": "2026-04-16 12:09:26.804 +0200",
        "lastUpdateBy": "luca.frosini",
        "lastUpdateTime": "2026-04-16 12:09:26.804 +0200"
    },
    "contexts": {
        "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
    },
    "consistsOf": [
        {
            "type": "IsIdentifiedBy",
            "supertypes": ["ConsistsOf"],
            "id": "641c1a36-da8d-4313-94f0-867453185d65",
            "metadata": {
                "createdBy": "luca.frosini",
                "creationTime": "2026-04-16 12:09:26.737 +0200",
                "lastUpdateBy": "luca.frosini",
                "lastUpdateTime": "2026-04-16 12:09:26.737 +0200"
            },
            "contexts": {
                "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
            },
            "propagationConstraint": {
                "type": "PropagationConstraint",
                "add": "propagate",
                "remove": "cascade"
            },
            "target": {
                "type": "SoftwareFacet",
                "supertypes": ["Facet"],
                "id": "cb6795ee-b729-47a7-ac9d-b805ae1df4d2",
                "metadata": {
                    "createdBy": "luca.frosini",
                    "creationTime": "2026-04-16 12:09:26.732 +0200",
                    "lastUpdateBy": "luca.frosini",
                    "lastUpdateTime": "2026-04-16 12:09:26.732 +0200"
                },
                "contexts": {
                    "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
                },
                "group": "test",
                "name": "test",
                "version": "1.0.0"
            }
        },
        {
            "type": "ConsistsOf",
            "id": "16395d26-47d5-419b-8562-4d7c8158e28a",
            "metadata": {
                "createdBy": "luca.frosini",
                "creationTime": "2026-04-16 12:09:26.759 +0200",
                "lastUpdateBy": "luca.frosini",
                "lastUpdateTime": "2026-04-16 12:09:26.759 +0200"
            },
            "contexts": {
                "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
            },
            "propagationConstraint": {
                "type": "PropagationConstraint",
                "add": "propagate",
                "remove": "cascade"
            },
            "target": {
                "type": "AccessPointFacet",
                "supertypes": ["Facet"],
                "id": "9d9a2a48-a82b-43d3-b499-6ce50e1e543c",
                "metadata": {
                    "createdBy": "luca.frosini",
                    "creationTime": "2026-04-16 12:09:26.755 +0200",
                    "lastUpdateBy": "luca.frosini",
                    "lastUpdateTime": "2026-04-16 12:09:26.755 +0200"
                },
                "contexts": {
                    "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
                },
                "endpoint": "https://www.d4science.org"
            }
        },
        {
            "type": "ConsistsOf",
            "id": "2e0ec586-667f-43b7-ba58-b750496a1f0b",
            "metadata": {
                "createdBy": "luca.frosini",
                "creationTime": "2026-04-16 12:09:26.784 +0200",
                "lastUpdateBy": "luca.frosini",
                "lastUpdateTime": "2026-04-16 12:09:26.784 +0200"
            },
            "contexts": {
                "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
            },
            "propagationConstraint": {
                "type": "PropagationConstraint",
                "add": "propagate",
                "remove": "cascade"
            },
            "target": {
                "type": "StateFacet",
                "supertypes": ["Facet"],
                "id": "45e42ef8-8275-4742-99f4-0cf7c8ecd862",
                "metadata": {
                    "createdBy": "luca.frosini",
                    "creationTime": "2026-04-16 12:09:26.779 +0200",
                    "lastUpdateBy": "luca.frosini",
                    "lastUpdateTime": "2026-04-16 12:09:26.779 +0200"
                },
                "contexts": {
                    "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
                },
                "date": "2022-11-30 11:32:38.959 +0100",
                "value": "ready"
            }
        }
    ]
}

Facet Instance

{
    "type": "SoftwareFacet",
    "supertypes": ["Facet"],
    "id": "cb6795ee-b729-47a7-ac9d-b805ae1df4d2",
    "metadata": {
        "createdBy": "luca.frosini",
        "creationTime": "2026-04-16 12:09:26.732 +0200",
        "lastUpdateBy": "luca.frosini",
        "lastUpdateTime": "2026-04-16 12:09:26.732 +0200"
    },
    "contexts": {
        "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
    },
    "group": "test",
    "name": "test",
    "version": "1.0.0"
}

IsRelatedTo Instance

(Note: The following represents a standalone Relation representation, read with metadataPolicy=all and contextsPolicy=all.)

Relation endpoint references: Outside the composition of a Resource instance, the source and target of a Relation instance are endpoint references: they identify the endpoint and say what kind of instance it is, but never describe it. An endpoint reference always carries type, supertypes, and id; it carries metadata only with metadataPolicy=all and contexts only with contextsPolicy=all; it never carries the endpoint’s custom fields, Facet instances, or relations. This holds for a standalone IsRelatedTo or ConsistsOf representation and for the elements of a full Operation Envelope. The only expanded endpoint is the target of a ConsistsOf instance nested in its Resource instance (the consistsOf field), which is the complete Facet instance, because the Facet instance is part of the Resource instance’s composition; its source is the enclosing Resource instance and is omitted.

{
    "type": "Uses",
    "supertypes": ["CallsFor", "IsRelatedTo"],
    "id": "8b3fdfaa-9daf-4791-9ca7-02f068153abc",
    "metadata": {
        "createdBy": "luca.frosini",
        "creationTime": "2026-04-16 12:09:26.732 +0200",
        "lastUpdateBy": "luca.frosini",
        "lastUpdateTime": "2026-04-16 12:09:26.732 +0200"
    },
    "contexts": {
        "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
    },
    "propagationConstraint": {
        "type": "PropagationConstraint",
        "add": "propagate",
        "remove": "cascade"
    },
    "source": {
        "type": "EService",
        "supertypes": ["Service", "GCubeResource", "Resource"],
        "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
        "metadata": {
            "createdBy": "luca.frosini",
            "creationTime": "2026-04-16 12:09:26.804 +0200",
            "lastUpdateBy": "luca.frosini",
            "lastUpdateTime": "2026-04-16 12:09:26.804 +0200"
        },
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
        }
    },
    "target": {
        "type": "EService",
        "supertypes": ["Service", "GCubeResource", "Resource"],
        "id": "2b99a46d-1111-4a17-bd2f-33450b109299",
        "metadata": {
            "createdBy": "luca.frosini",
            "creationTime": "2026-04-16 12:09:26.814 +0200",
            "lastUpdateBy": "luca.frosini",
            "lastUpdateTime": "2026-04-16 12:09:26.814 +0200"
        },
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
        }
    }
}

ConsistsOf Instance

{
    "type": "IsIdentifiedBy",
    "supertypes": ["ConsistsOf"],
    "id": "641c1a36-da8d-4313-94f0-867453185d65",
    "metadata": {
        "createdBy": "luca.frosini",
        "creationTime": "2026-04-16 12:09:26.737 +0200",
        "lastUpdateBy": "luca.frosini",
        "lastUpdateTime": "2026-04-16 12:09:26.737 +0200"
    },
    "contexts": {
        "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
    },
    "propagationConstraint": {
        "type": "PropagationConstraint",
        "add": "propagate",
        "remove": "cascade"
    },
    "source": {
        "type": "EService",
        "supertypes": ["Service", "GCubeResource", "Resource"],
        "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
        "metadata": {
            "createdBy": "luca.frosini",
            "creationTime": "2026-04-16 12:09:26.804 +0200",
            "lastUpdateBy": "luca.frosini",
            "lastUpdateTime": "2026-04-16 12:09:26.804 +0200"
        },
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
        }
    },
    "target": {
        "type": "SoftwareFacet",
        "supertypes": ["Facet"],
        "id": "cb6795ee-b729-47a7-ac9d-b805ae1df4d2",
        "metadata": {
            "createdBy": "luca.frosini",
            "creationTime": "2026-04-16 12:09:26.732 +0200",
            "lastUpdateBy": "luca.frosini",
            "lastUpdateTime": "2026-04-16 12:09:26.732 +0200"
        },
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube"
        }
    }
}

Query Templates Representation

The Resource Registry query engine natively supports JSON Query-By-Example (JSON-QBE), adapting the classical QBE paradigm[^zloof-1975] for document graphs. This paradigm allows clients to retrieve data by providing a JSON fragment that acts as a structural blueprint of the desired result, rather than writing strings of SQL-like query language.

Query Templates elevate this concept by allowing administrators to define pre-packaged, parameterized JSON-QBE payloads that clients can execute simply by name, without needing to know the underlying structural complexity. The exact, case-sensitive name is the template’s stable public identity and the sole routing key in every API endpoint, following the same identity model as Types. The id UUID remains visible in the representation and participates in creation and update consistency checks, but it is never an addressable public identity.

Example: Query Template Definition

{
    "type": "QueryTemplate",
    "id": "61454553-5b0c-4e39-b848-b0fe4be4d9da",
    "metadata": {
        "createdBy": "luca.frosini",
        "creationTime": "2025-05-30 19:29:13.020 +0200",
        "lastUpdateBy": "luca.frosini",
        "lastUpdateTime": "2025-05-30 19:29:20.241 +0200"
    },
    "name": "get-all-eservice-with-state",
    "description": "Returns all EService instances with the provided state.",
    "version": "1.0.0",
    "changelog": { "1.0.0": "First Version" },
    "template": {
        "type": "EService",
        "consistsOf": [
            {
                "type": "ConsistsOf",
                "target": {
                    "type": "StateFacet",
                    "value": "$state"
                }
            }
        ]
    },
    "templateVariables": {
        "$state": {
            "type": "TemplateVariable",
            "name": "$state",
            "description": "The state of the EService, e.g. down, ready.",
            "propertyType": "String",
            "defaultValue": "down",
            "required": false,
            "regexp": ""
        }
    }
}

Field Explanations

  • template: A JSON-by-Example payload representing the structural query. In this case, it looks for any EService instance that has a ConsistsOf instance pointing to a StateFacet instance. The value uses a parameter $state.

  • name: Lowercase words separated by hyphens, matching ^[a-z][a-z0-9]*(-[a-z0-9]+)*$ (see [Naming Conventions](12-is-architectural-decisions.md#naming-conventions)); a create or update with any other name returns 400 Bad Request. The exact, case-sensitive stable public identity used to create, read, execute, update, and delete this Query Template.

  • id: The representation UUID. It may be supplied on creation or generated by the server and may be repeated later as a consistency assertion, but it is never used for routing.

  • version & changelog: Mandatory. They use the format and semantics of the Type schema fields of the same name (see [Type Schema fields](05-data-representation-contexts-schemas.md)): version matches ^[1-9][0-9]*\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ and is ordered numerically component by component, and changelog maps each version to a non-empty description of its change. As for a Type, version must be the highest version listed in changelog: a new version is therefore always submitted together with its own changelog entry. The map is the complete history: an update repeats every persisted entry unchanged and adds the new ones. The version decides the outcome of a write, as defined by the [Versioned Upsert Semantics](10-api-query-templates-and-access.md#operational-notes).

  • templateVariables: A dictionary defining all bindable parameters (e.g., $state) used by the template. It is optional: when it is omitted or null, the Query Template has no variables and the field is returned as {}. Its complete contract follows.

TemplateVariable Contract

Each map key and its nested name must be identical and match ^\$[a-z][a-zA-Z0-9]{0,63}$: a $ followed by a lowerCamelCase name, with no underscore, which is reserved as the operator prefix. The canonical object fields are:

| Field | Required | Contract | |—|—:|—| | name | Yes | Exact duplicate of the containing map key. | | description | Yes | Human-readable string. | | propertyType | Yes | One of String, Boolean, Integer, Double, Float, or Date. | | defaultValue | Yes | Non-null value valid for propertyType and every declared constraint. It is used for registration-time validation and, when required=false, as the runtime fallback. It cannot be a string starting with $. | | required | No | Boolean, default false. When true, the execution payload must supply the variable; its default remains registration-only. | | min | No | Inclusive numeric lower bound for numeric variables. An integer, also for Double and Float variables. | | max | No | Inclusive numeric upper bound for numeric variables; for String, maximum length in UTF-16 code units. An integer in both cases. | | regexp | No | Regular expression for String, satisfied when the pattern matches any part of the value; defaults to the empty expression (no additional pattern constraint). |

A returned variable always carries type (TemplateVariable), required, and regexp, with their default values when the submitted definition omits them; a type or supertypes submitted on a variable is accepted.

Values use strict native JSON types and are never coerced. String and Date use JSON strings, Boolean uses a JSON boolean, and numeric types use JSON numbers; a numeric-looking string is invalid. Integer is limited to the signed 32-bit range, floating-point values must be finite, and Date uses yyyy-MM-dd HH:mm:ss.SSS Z. An explicit JSON null is invalid. min applies to numeric variables only, max to numeric variables and to String (as maximum length), and regexp to String only; any other combination (for example min on a String or Date, regexp on a number, or any of them on a Boolean), and min > max, is invalid. Without an explicit max, a String value is limited to 1024 UTF-16 code units. Embedded \u0000 characters are invalid.

Every string node in template that exactly equals a declared variable name is replaced recursively with the variable’s complete JSON value node. Partial interpolation such as "prefix-$state" is not supported, inserted values are not scanned again, and object keys are not substitution targets. Every string node that starts with $ is a variable reference, so a template cannot contain a literal string value starting with $. Every variable reference must be declared, and every declaration must be referenced at least once; both conditions are validated when the Query Template is saved.

PUT /query-templates/{name} validates the declaration schema, names, types, constraints, references, and defaults, substitutes all defaults, and validates the resulting query before persisting it. At execution time, the body must be absent or a JSON object. An omitted required=false variable uses its default; omission of a required=true variable returns 400 Bad Request. Supplied values are validated before substitution. Undeclared input keys are ignored and reported through X-Warning; declaration names and input keys are case-sensitive.

Schema-Mixed Encrypted Fields

Because the IS-Model allows dynamic injection of fields (Schema-Mixed Mode), the Resource Registry provides a native Encrypted complex Property Type to handle sensitive dynamic data without relying on external vaults.

What a client observes is the following. An Encrypted value always travels encrypted with the symmetric key of the Context of the request: a client writes it and reads it with that key. The same value read from two different Contexts therefore arrives as two different ciphertexts, each one decipherable only with the key of its own Context, and both yield the same plain value. When an instance is added to another Context, that Context reads the value with its own key and no rewrite of the value is needed.

This is the result of a “double-encryption” architecture, which ensures both data-at-rest security and strict transport isolation:

  1. Write (Storage): When a client submits an Encrypted field, they provide it encrypted with the symmetric key of their operational Context. The backend service receives it, decrypts it using that specific Context key, and then re-encrypts the raw value using the service’s own universal service-managed encryption key before committing it to the database. The data on the database is therefore uniquely encrypted by the service.

  2. Read (Retrieval): Upon retrieval, the backend decrypts the value using its universal service-managed encryption key, and dynamically re-encrypts it on the fly using the symmetric key of the specific Context requested by the client (or the Context tied to the caller’s token).

Multi-Context Sharing & Hierarchical Semantics: This architecture elegantly resolves the complexities of sharing secrets across tenants without cryptographic leakage:

  • Sharing into a new Context (Propagation): Because the actual data at rest is encrypted solely with the universal service-managed encryption key, sharing a secret-bearing instance (e.g., propagating a Facet instance to a new Context) requires zero payload mutation. The backend simply adds the new Context to the instance’s visibility. Propagation automatically grants the new Context access.

  • Hierarchical Reads: If a manager performs a hierarchical read spanning multiple Contexts (?hierarchical=true), the system dynamically re-encrypts all returned secrets using the symmetric key of the Context bound to the caller’s operational token, ensuring the caller can seamlessly decrypt the entire payload with their single key.

(Note: How the upstream API Gateway, such as SmartGears, provisions or retrieves these symmetric Context keys to decrypt the incoming payload from the client is an infrastructure concern handled by the Gateway itself, and falls outside the Resource Registry API contract. Similarly, it is explicitly out of scope for this API guide to document the mechanisms for Context key issuance, cryptographic key rotation protocols, and the subsequent batch re-encryption of persisted data).

⚠️ Roadmap Warning: While effective, this double-encryption mechanism represents an interim architectural pattern. In future iterations, secret management will be offloaded to a dedicated Vault service via reference pointers. Client integrators should encapsulate their encryption/decryption routines to minimize refactoring when this transition occurs.

Operation Envelope Representation

When performing membership operations (such as Add to Context or Remove from Current Context), the system returns an Operation Envelope. This response describes the set of instances affected by the “avalanche effect”. The density of this envelope depends on the Prefer: return HTTP header.

Both forms have the same structure: a flat array with one element for every affected instance, Resource, Facet, IsRelatedTo, and ConsistsOf alike. No element is nested inside another. Every IsRelatedTo and ConsistsOf element carries its source and target, so that a client can always tell which instances a relation connects. The two forms differ only in how much of each element they return. After a removal, the envelope lists the instances that left the Context and does not distinguish those that were also deleted because it was their last Context.

Minimal Operation Envelope (Prefer: return=minimal)

A minimal operation envelope returns a flat array of instances, containing only their type and id (along with source and target for Relation instances, each reduced to type and id). It strictly omits supertypes, metadata, contexts, and any custom data fields, providing a highly optimized payload for bulk synchronization checks.

[
    {
        "type": "EService",
        "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
    },
    {
        "type": "IsIdentifiedBy",
        "id": "a8353fdf-9daf-4791-9ca7-02f068153ad0",
        "source": {
            "type": "EService",
            "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
        },
        "target": {
            "type": "SoftwareFacet",
            "id": "e32275d1-1da1-4f47-8c7d-e34a23f4330a"
        }
    },
    {
        "type": "SoftwareFacet",
        "id": "e32275d1-1da1-4f47-8c7d-e34a23f4330a"
    },
    {
        "type": "ConsistsOf",
        "id": "6ca7ee85-19d1-4f5d-8fd7-f2f399a6beeb",
        "source": {
            "type": "EService",
            "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
        },
        "target": {
            "type": "EventFacet",
            "id": "c3a8bf2a-788c-486f-949f-2f72be56b479"
        }
    },
    {
        "type": "EventFacet",
        "id": "c3a8bf2a-788c-486f-949f-2f72be56b479"
    },
    {
        "type": "ConsistsOf",
        "id": "78696361-0d73-4762-8b7b-c4bb203f52dc",
        "source": {
            "type": "EService",
            "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
        },
        "target": {
            "type": "StateFacet",
            "id": "0037c9b9-e75b-4f62-9400-234b902823e2"
        }
    },
    {
        "type": "StateFacet",
        "id": "0037c9b9-e75b-4f62-9400-234b902823e2"
    },
    {
        "type": "ConsistsOf",
        "id": "c616b059-0677-4628-9f7d-f01a44b54069",
        "source": {
            "type": "EService",
            "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
        },
        "target": {
            "type": "AccessPointFacet",
            "id": "52b45cb3-f691-4f30-952e-0bfa875ad7a8"
        }
    },
    {
        "type": "AccessPointFacet",
        "id": "52b45cb3-f691-4f30-952e-0bfa875ad7a8"
    }
]

Full Representation Operation Envelope (Prefer: return=representation)

A full operation envelope returns the same flat array as the minimal one, with every element in its own complete representation: type, supertypes, id, the custom fields of a Facet, and the propagationConstraint and custom fields of a IsRelatedTo or ConsistsOf instance, whose source and target are endpoint references (see Relation endpoint references above).

metadata and contexts are added only when requested through metadataPolicy and contextsPolicy: root adds them to every element of the array, all also to the endpoint references of the relations. membershipScope independently selects whether each emitted contexts map contains memberships from the token Context subtree or, for authorized universal roles, from the global Context tree.

(Example with contextsPolicy=root; the metadata blocks are omitted.)

[
    {
        "type": "EService",
        "supertypes": [ "Service", "GCubeResource", "Resource" ],
        "id": "1a50a46d-2210-4a17-bd2f-22450b109264",
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube",
            "a7bc333f-84df-498d-ae9d-748b358267b5": "/gcube/devsec"
        }
    },
    {
        "type": "IsIdentifiedBy",
        "supertypes": [ "ConsistsOf" ],
        "id": "a8353fdf-9daf-4791-9ca7-02f068153ad0",
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube",
            "a7bc333f-84df-498d-ae9d-748b358267b5": "/gcube/devsec"
        },
        "propagationConstraint": {
            "type": "PropagationConstraint",
            "add": "propagate",
            "remove": "cascade"
        },
        "source": {
            "type": "EService",
            "supertypes": [ "Service", "GCubeResource", "Resource" ],
            "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
        },
        "target": {
            "type": "SoftwareFacet",
            "supertypes": [ "Facet" ],
            "id": "e32275d1-1da1-4f47-8c7d-e34a23f4330a"
        }
    },
    {
        "type": "SoftwareFacet",
        "supertypes": [ "Facet" ],
        "id": "e32275d1-1da1-4f47-8c7d-e34a23f4330a",
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube",
            "a7bc333f-84df-498d-ae9d-748b358267b5": "/gcube/devsec"
        },
        "name": "test",
        "version": "1.0.0",
        "group": "test"
    },
    {
        "type": "ConsistsOf",
        "id": "78696361-0d73-4762-8b7b-c4bb203f52dc",
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube",
            "a7bc333f-84df-498d-ae9d-748b358267b5": "/gcube/devsec"
        },
        "propagationConstraint": {
            "type": "PropagationConstraint",
            "add": "propagate",
            "remove": "cascade"
        },
        "source": {
            "type": "EService",
            "supertypes": [ "Service", "GCubeResource", "Resource" ],
            "id": "1a50a46d-2210-4a17-bd2f-22450b109264"
        },
        "target": {
            "type": "StateFacet",
            "supertypes": [ "Facet" ],
            "id": "0037c9b9-e75b-4f62-9400-234b902823e2"
        }
    },
    {
        "type": "StateFacet",
        "supertypes": [ "Facet" ],
        "id": "0037c9b9-e75b-4f62-9400-234b902823e2",
        "contexts": {
            "4eb849d5-efbb-4430-9eb6-1968123921b4": "/gcube",
            "a7bc333f-84df-498d-ae9d-748b358267b5": "/gcube/devsec"
        },
        "value": "ready"
    }
]

(Note on StateFacet vs EventFacet: In the examples throughout this document, StateFacet uses a simple value field to represent a scalar state, whereas EventFacet uses an event field. This is an intentional schema difference: EventFacet is designed to be backed by the complex Event property schema (which can encompass the “Five Ws” tracking fields), while StateFacet represents a simplified enumeration).