Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -580,24 +580,27 @@ The asset metadata is stored in the metadata store, using the following data mod
|===
| Type | Column | Key | Note

| VARCHAR | asset_hash | PK |
| VARCHAR | subjectid | |
| VARCHAR | issuer | |
| BIGINT | id | PK | Surrogate key, generated by `assets_id_seq` sequence
| VARCHAR(64) | asset_hash | UQ | Content hash — unique constraint (`uq_assets_asset_hash`)
| TEXT | subjectid | |
| TEXT | issuer | |
| TIMESTAMP | uploadtime | |
| TIMESTAMP | statustime | |
| TIMESTAMP | expirationtime | |
| CLOB | content | | nullable — NULL for non-RDF assets
| SMALLINT | status | | Lifecycle status ordinal (0=ACTIVE, 1=DEPRECATED, …)
| TEXT | content | | nullable — NULL for non-RDF assets
| VARCHAR[] | validators | |
| VARCHAR | content_type | | MIME type (e.g. application/pdf)
| BIGINT | file_size | | asset size in bytes
| VARCHAR | original_filename | | client-provided filename
| VARCHAR(500) | original_filename | | client-provided filename
|===

.Asset table notes
****
. The table was renamed from `sdfiles` to `assets` and the primary key from `sdhash` to `asset_hash` (see Liquibase changeset `2026-03-09-rename-sdfiles-to-assets`).
. The table was renamed from `sdfiles` to `assets` and the primary key from `sdhash` to `asset_hash` (see Liquibase changeset `2026-03-09-rename-sdfiles-to-assets`). A subsequent migration (`2026-03-25-technical-ids-assets`) introduced a surrogate `BIGINT` primary key (`id`) with a sequence, demoting `asset_hash` to a unique constraint.
. The `content` column is nullable. For credentials (RDF), it stores the JSON-LD in the database. For non-RDF assets, content is stored only in the File Store and the database column remains `NULL`. The three asset metadata columns (`content_type`, `file_size`, `original_filename`) are backfilled with `content_type = 'application/ld+json'` for existing records.
. The `subjectid` column stores the asset's IRI (see ADR 7) — for RDF assets this is the extracted `credentialSubject.id` or equivalent; for non-RDF assets this is a UUID URN generated by `IriGenerator`.
. The table is audited by Hibernate Envers — all insert, update, and delete operations are tracked in the `assets_aud` shadow table (see <<_audit_history>>).
****

==== Schema Management Store
Expand Down Expand Up @@ -708,34 +711,55 @@ public interface SchemaStore {
* @return The union RDF graph.
*/
ContentAccessor getCompositeSchema(SchemaType schemaType);

/**
* Get the content of a specific historical version of a schema.
*
* @param identifier The identifier of the schema.
* @param version The 1-based version number to retrieve.
* @return The schema content at the given version.
*/
ContentAccessor getSchemaVersion(String identifier, int version);

/**
* Get the version history for the schema with the given identifier.
*
* @param identifier The identifier of the schema.
* @return Ordered list of schema records, one per version (ascending).
*/
List<SchemaRecord> getSchemaVersions(String identifier);
}
----

Schema versioning is implemented via Hibernate Envers (see <<_audit_history>>). Every `PUT /schemas/{id}` call triggers a JPA `save()`, causing Envers to append a snapshot to `schemafiles_aud`. The `SchemaAuditRepository` component (DAO layer) encapsulates all Envers API calls (`AuditReaderFactory`, `AuditEntity` queries), keeping Envers types out of `SchemaJpaDao`. Versions exposed through the API are 1-based positional ordinals computed over per-schema revisions in ascending order.

The schema metadata is stored in the metadata store, using the following data model:

[mermaid, width=2000]
....
erDiagram
SCHEMAFILE {
VARCHAR schemaid
BIGINT id PK "sequence: schemafiles_id_seq"
VARCHAR schemaid UK "unique business key"
VARCHAR namehash
TIMESTAMP uploadtime
TIMESTAMP updatetime
INTEGER type
CLOB content
TIMESTAMP created_at
TIMESTAMP modified_at
VARCHAR type "ONTOLOGY | SHAPE | VOCABULARY"
TEXT content
}

SCHEMATERM {
VARCHAR term
VARCAHR schemaid
BIGINT id PK "sequence: schematerms_id_seq"
VARCHAR term "globally unique by DB constraint"
BIGINT schema_file_id FK
}

REVALIDATORCHUNK {
INTEGER chunkid
INTEGER chunkid PK
TIMESTAMP lastcheck
}

SCHEMAFILE || -- o{ SCHEMATERM: defines
SCHEMAFILE ||--o{ SCHEMATERM : defines
....

==== Validator Cache
Expand Down Expand Up @@ -797,6 +821,134 @@ erDiagram

....

[[_audit_history]]
==== Audit History (Hibernate Envers)

The catalogue uses Hibernate Enversfootnote:[https://hibernate.org/orm/envers/[Hibernate Envers — Entity Auditing]] to maintain an audit trail for assets and schemas. Every insert, update, and delete on an audited entity is recorded in a corresponding `_aud` shadow table, linked to a global revision in the `REVINFO` table.

Audited entities: `Asset` (`assets`), `SchemaFile` (`schemafiles`), `SchemaTerm` (`schematerms`).

[mermaid, width=2000]
....
erDiagram
REVINFO {
INTEGER rev PK "sequence: revinfo_seq"
BIGINT revtstmp "epoch millis"
}

ASSETS_AUD {
BIGINT id PK
INTEGER rev PK_FK
SMALLINT revtype "0=INSERT 1=UPDATE 2=DELETE"
VARCHAR asset_hash
TEXT subjectid
TEXT issuer
TIMESTAMP uploadtime
TIMESTAMP statustime
TIMESTAMP expirationtime
SMALLINT status
TEXT content
VARCHAR content_type
BIGINT file_size
VARCHAR original_filename
}

SCHEMAFILES_AUD {
BIGINT id PK
INTEGER rev PK_FK
SMALLINT revtype
VARCHAR schemaid
VARCHAR namehash
TIMESTAMP uploadtime
TIMESTAMP updatetime
VARCHAR type
TEXT content
}

SCHEMATERMS_AUD {
BIGINT id PK
INTEGER rev PK_FK
SMALLINT revtype
VARCHAR term
BIGINT schema_file_id
}

REVINFO ||--o{ ASSETS_AUD : tracks
REVINFO ||--o{ SCHEMAFILES_AUD : tracks
REVINFO ||--o{ SCHEMATERMS_AUD : tracks
....

.Audit table notes
****
. Each `_aud` table mirrors the columns of its source table plus `rev` (FK to REVINFO) and `revtype` (0=INSERT, 1=UPDATE, 2=DELETE). The composite primary key is `(id, rev)`.
. Audit tables are managed by Liquibase (changeset `2026-03-25-envers-*`), not by Hibernate's `hbm2ddl` auto-generation.
. Known limitation: bulk JPQL `DELETE` or `UPDATE` statements bypass Envers because they do not trigger JPA entity lifecycle events. All current DAO code uses entity-level operations, so this limitation does not apply in practice.
****

[[_audit_history]]
==== Audit History (Hibernate Envers)

The catalogue uses Hibernate Enversfootnote:[https://hibernate.org/orm/envers/[Hibernate Envers — Entity Auditing]] to maintain an audit trail for assets and schemas. Every insert, update, and delete on an audited entity is recorded in a corresponding `_aud` shadow table, linked to a global revision in the `REVINFO` table.

Audited entities: `Asset` (`assets`), `SchemaFile` (`schemafiles`), `SchemaTerm` (`schematerms`).

[mermaid, width=2000]
....
erDiagram
REVINFO {
INTEGER rev PK "sequence: revinfo_seq"
BIGINT revtstmp "epoch millis"
}

ASSETS_AUD {
BIGINT id PK
INTEGER rev PK_FK
SMALLINT revtype "0=INSERT 1=UPDATE 2=DELETE"
VARCHAR asset_hash
TEXT subjectid
TEXT issuer
TIMESTAMP uploadtime
TIMESTAMP statustime
TIMESTAMP expirationtime
SMALLINT status
TEXT content
VARCHAR content_type
BIGINT file_size
VARCHAR original_filename
}

SCHEMAFILES_AUD {
BIGINT id PK
INTEGER rev PK_FK
SMALLINT revtype
VARCHAR schemaid
VARCHAR namehash
TIMESTAMP created_at
TIMESTAMP modified_at
VARCHAR type
TEXT content
}

SCHEMATERMS_AUD {
BIGINT id PK
INTEGER rev PK_FK
SMALLINT revtype
VARCHAR term
BIGINT schema_file_id
}

REVINFO ||--o{ ASSETS_AUD : tracks
REVINFO ||--o{ SCHEMAFILES_AUD : tracks
REVINFO ||--o{ SCHEMATERMS_AUD : tracks
....

.Audit table notes
****
. Each `_aud` table mirrors the columns of its source table plus `rev` (FK to REVINFO) and `revtype` (0=INSERT, 1=UPDATE, 2=DELETE). The composite primary key is `(id, rev)`.
. Audit tables are managed by Liquibase (changeset `2026-03-25-envers-*`), not by Hibernate's `hbm2ddl` auto-generation.
. Known limitation: bulk JPQL `DELETE` or `UPDATE` statements bypass Envers because they do not trigger JPA entity lifecycle events. All current DAO code uses entity-level operations, so this limitation does not apply in practice.
****

[[_verification_profiles]]
==== Verification Profiles

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,8 @@ sequenceDiagram

==== Get a Schema

The optional `?version=X` query parameter retrieves a specific historical version. Without it, the current version is returned. A non-existent version number returns HTTP 404.

[mermaid, width=2000]
....
sequenceDiagram
Expand All @@ -745,14 +747,37 @@ sequenceDiagram
participant SchemaStore as SchemaStore
User ->> + Auth:Authorize(SCHEMA_READ | ADMIN_ALL)
Auth -->> - User:OK
User ->> + API:GET /schemas/{schemaId}
API ->> + SchemaStore:getSchema(schemaId)
SchemaStore ->> SchemaStore:load Schema-file
SchemaStore -->> - API:Schema-file
User ->> + API:GET /schemas/{schemaId}[?version=X]
alt version parameter present
API ->> + SchemaStore:getSchemaVersion(schemaId, version)
SchemaStore -->> - API:Schema-file at version X (or 404)
else no version parameter
API ->> + SchemaStore:getSchema(schemaId)
SchemaStore -->> - API:Schema-file (current version)
end
API -->> - User:Schema-file
....


==== Get Schema Version History

[mermaid, width=2000]
....
sequenceDiagram
actor User
participant Auth as Authentication
participant API
participant SchemaStore as SchemaStore
User ->> + Auth:Authorize(SCHEMA_READ | ADMIN_ALL)
Auth -->> - User:OK
User ->> + API:GET /schemas/{schemaId}/versions
API ->> + SchemaStore:getSchemaVersions(schemaId)
SchemaStore -->> - API:List<SchemaRecord> (ascending by version)
API ->> API:map to SchemaVersionList {schemaId, versions[]}
Note right of API: Each entry: version (1-based), createdAt, isCurrent
API -->> - User:SchemaVersionList
....

==== Get the Schema that defines an Entity

[mermaid, width=2000]
Expand Down