Skip to content

Model Registry Service Design

Functional Specifications

Model Naming Conventions

Each model registered in the service registry should have a unique, human readable, concise and informative name, making it easy to understand the model's content at a glance.

Since in Harbor it is possible to group repositories by projects it is reasonable to use the same model organization structure in the Model Registry service as well. According to that the full model name can have the following format:

[registry address]/[project name]/[image name]/[tag]

Name Part Description
registry address The obligatory part that specifies which registry should be used to upload the model. The corresponding Harbor instance should be used to push a model to the local Harbor registry.
project name Should be exactly the same as the target Harbor project name. Also the project name corresponds to a tenant and allows keeping of models separately and with authorised access to them.
image/model name A free text that should explain and describe the model’s content.
tag A unique model version identifier.

There are the following rules should be applied for proper models naming:

  • Model Name Specification:

    • The model name should generally be lowercase to prevent case-sensitivity issues across different systems and registries.
    • Any special characters (beyond allowed) must be avoided: only hyphens (-), underscores (_), and periods (.) are allowed in image names. All other special characters must be avoided for better compatibility and readability.
  • Tag Name Specification:

    • Semantic versioning is a widely used versioning scheme that uses a three-part version number consisting of major, minor, and patch versions. The version is typically written in the format “major.minor.patch”. It should be applied for tags as well.

Each model update should lead to a version update to keep track of every change made to the codebase or model dependencies. Clear version histories allow quick rollbacks if something goes wrong without losing progress.

Lifecycle Management

The following phases of the model life cycle are used for efficient allocation of finite infrastructure resources and minimization maintenance overheads. An organized pipeline keeps everyone aligned towards common objectives.

Phase Name Description
Image Creation The image is built and wrapped using XUMI functionality. The expected model functionality is verified and the model is ready for publishing.
Image tag assignment An appropriate image tag is assigned to the image by the user as a required step before pushing the model to the corresponding registry.
Model signing and pushing Such signing tools as Cosign and Notation typically perform an image signing operation simultaneously with pushing an image to the specified registry.
Image is stored in local registry The model is stored in a local Harbor local registry for sharing and distribution. Images are versioned using tags to track changes and facilitate rollbacks.Each tag contains a project name which owns this particular model.
Automatic model scanning Once a model is pushed to the local Harbor storage the default security scanner is activated and verifies the recently added image for vulnerabilities
Deployment to Remote Registry The scanned and approved (signed) model is uploaded to a remote registry and becomes available for .
Model Updates and Maintenance The model is rebuilt when the underlying application code or dependencies are changed or when detected vulnerabilities should be addressed. It leads to a new model version creation and starting the model life cycle from the beginning.
Model download and execution A verified and approved model that is available in a remote storage can be used by users who have authorized access to it. They can pull the model and execute it.
Model changes synchronization Handle conflicts of detected version mismatches in a remote container. Model versions synchronization is performed in automatic mode when a newer model version appears in a remote registry and it is absent in the local registry.
Image cleanup and deletion Old or unused images consume disk space and should be removed based on specific rules.

Model life cycle is depicted on the diagram below.

Model Lifecycle

Note

The required step for the model scanning is combined with model deployment to a remote registry since such remote containers as Harbor have in-built possibility to verify uploaded models for vulnerabilities.

Security and Access Control

Policy-based access control (PBAC) is required to allow access for those users who should create, modify, or remove models. Strict authentication protocols (JWT) must be enforced when communicating with external or internal services. Please refer to the corresponding document for more information - Role-based access system architecture.

Metadata Tagging

In addition to versioning metadata provides contextual insights about the origin, purpose, and characteristics of each model version. It serves both diagnostic purposes (debugging issues) and strategic decision-making. The additional metadata can be stored alongside each uploaded model, including timestamps, owner information, model checkpoints, dataset references, runtime requirements, and custom labels.

Checkpoint Management

Checkpoints are closely linked to the model entity. Unlike models, checkpoints are managed and monitored using the Assets service. Future plans include the integration of checkpoint versions into the Model Registry service.

Application Architecture

Overview

The service is based on a close integration with the Harbor project. Harbor is an open-source registry that provides secure storage and management for container images, offering features like vulnerability scanning, policy enforcement, and role-based access control.

Key Features

  • Clear models structure grouped by projects (tenants)
  • Organized models life cycle control and versioning.
  • Tracking information about models using Harbor web hooks
  • Simplified and straightforward projects management through Harbor API
  • Configurable and secure models sharing control via Harbor replication rules
  • Configurable semi-automatic or automatic registry cleanup functionality
  • Harbor in-built scanning and signing functionality with comprehensive reports providing with regard to a particular model version
  • Role-based access control for all service data and actions
  • RESTful API with consistent endpoints for all the functionality above

The Model Registry service represents a standalone web application based on a common multi-layered architecture that provides access to the specified functionality for other services and UI via gRPC and REST API.

Service Components

Components Diagram

Component Name Description
Authorization Middleware The component that is used for authorization and secure execution of the service API methods. Uses an external PBAC service for user credentials verification.
Harbor Web Hooks Controller The endpoint for catching all web hooks from the Harbor instance. Is a single way to retrieve the required information about changes in the Harbor registry.
Project Controller The controller to manage projects. Simplifies creation and configuration of new Harbor projects.
Registry Controller The controller to manage remote registries (endpoints) and establish links between model versions and remote registries.
Model Controller The controller to manage models data. Allows to retrieve the models related information and track additional models versions/metadata, resource requirements, etc.
Checkpoint Controller The controller to track model checkpoints. A checkpoint is linked to a model version entity and should refer to external or internal asset files.
Cleanup Controller The controller for getting a list of outdated or obsolete models and model versions. Also allows to setup and run the supported cleanup activities in the local Harbor registry.
Audit Log Controller Allows to get details about scanning and signing results for a particular model.
Users and Groups Controller Provides the set of methods to manage project members, add and delete users accounts and their roles.
Synchronization Service The component that allows updating the corresponding models data to keep it up to date.
Harbor API Client The module that handles all interactions with the Harbor instance through its API and returns the received results.
Model Metadata Service The service that is intended to extract the required metadata information from a model’s manifest to keep all the required data in the service database.
Web Hooks Queue Service The service that processes the web hooks queue (a separate database table) in the service database in the background, parses all retrieved data from the harbor and updates the corresponding service records.
Cleanup Service Implements the functionality that is required for the Cleanup Controller. Gathers and returns information about obsolete model versions and allows execution of the supported cleanup activities in the local Harbor registry.

Integration with the Harbor Registry

Integration with the Harbor can be organized in two possible ways listed below.

  • Single Harbor Instance Model

Advantages:

  • Simple solution, since the Model Registry service interacts with the single Harbor instance and all users can login in a single Docker registry to obtain models
  • Each client/tenant owns a project with models
  • Public projects can contain shared models (resources)

Disadvantages:

  • It’s impossible to support any nested levels such as client’s departments or teams since project is an indivisible entity in Harbor
  • Need to separate all users from the Keycloak to individual projects to guarantee proper access control and make sure that each user can have access to an allowed set of models (project)

Advantages:

  • More complex solution, since the Model Registry service has to manage multiple Harbor instances
  • Each Harbor instance represents a particular tenant (client) in this case and there is a possibility to support one additional nested level and manage such entities as client’s departments or teams using separation by projects
  • Public projects can contain shared models (resources)
  • More reliable delimitation of users since all client’s users have their own instance with configured permissions

Disadvantages:

  • Additional infrastructure costs to deploy and configure a separate Harbor instance for each client (tenant)
  • Need to separate all users from the Keycloak to individual Harbor instances to guarantee proper access control and make sure that each user can have access to an allowed Harbor registry

At the current moment the single instance Harbor model looks more acceptable due to its simplicity and less efforts and costs to deploy and support it but multi instance Harbor model provides more abilities and more isolated users separation.

Synchronization with Remote Registries

Harbor projects regardless of their access level (public or private) can be created in usual or “proxy” mode. Proxy Harbor project is used to proxy and cache images from a target public or private registry. Regrettably “proxy” project type has a set of limitations that makes it not suitable for the project purposes:

  • The “proxy” project does not have Deployment Security settings, i.e. it is not possible to enable the policy for obligatory images signing and prohibit usage and running not approved models by users
  • According to the documentation Harbor only supports proxy caching for Docker Hub and Harbor registries. It is narrowing the list of possible integrations with other container registries.
  • A proxy cache project works similarly to a normal Harbor project, except that you are not able to push images to a proxy cache project.

These limitations are significant and each of them is a sufficient reason to abandon usage of proxy cache projects. They can be useful to pull images from a target Harbor or non-Harbor registry in an environment with limited or no access to the internet. You can also use a proxy cache to limit the amount of requests made to a public registry, avoiding consuming too much bandwidth or being throttled by the registry server.

A typical Harbor project looks more interesting since it allows pulling and pushing images according to the user role, supports images signing and scanning and provides great replication possibilities for synchronization with remote registries of defined types.

There are two possible replication methods that are available in Harbor:

  • Push-based - synchronizes the resources from the local Harbor registry to a remote registry.
  • Pull-based - downloads the resources from the remote registry to the local Harbor registry.

Besides that it is possible to trigger both types of replication manually, based on the event, or by schedule which allows flexible configuration of models and their versions synchronization between local and remote registries. So, full-fledged typical Harbor project type with enabled forward and backward replication will cover all system needs of a fully functional images secure registry with ability to share models according to the specified rules.

Users and Access Management within a Multi-Tenant Environment

Since there is no Harbor API to directly create a user when OIDC authentication is enabled as this would bypass the OIDC provider. Instead of this it is necessary to focus on managing users and their attributes within the OIDC provider and then manage project roles via Harbor REST API after users have been onboarded.

  1. Each tenant most likely has its own identity provider that could be registered in Keycloak as OIDC provider. Keycloak allows multiple providers and different flows of users could be separated using Groups Membership and exchange groups info through security tokens.
  2. Users groups are promoted automatically in Harbor and can be mapped with the corresponding roles to an appropriate project (tenant). Authenticated users that have been onboarded and automatically assigned to the project can use their credentials to connect to the Harbor registry through Docker CLI.
  3. The project and project’s membership must be configured preliminary to map new user accounts automatically.

The sequence diagram above depicts the user registration scenario which can be triggered by manual user sign-in event. To add a user who can access Harbor via OIDC, it is necessary to create that user account within an appropriate OIDC identity provider such as Keycloak, Okta, or Microsoft Entra, etc.

Robot Accounts for Scripting and Automation

Harbor allows you to use a system or project specific robot account to automate running operations for a project including:

  • Push artifacts
  • Pull artifacts
  • Delete artifacts
  • Read Helm charts
  • Create a Helm chart version
  • Delete a Helm chart version
  • Create a tag
  • Delete a tag
  • Create artifact labels
  • Create a scan

A project robot account authenticates to a Harbor instance using a secret, allowing you to connect to a Harbor instance through the OCI client or Harbor API to automate tasks.

Note: Robot Accounts cannot log in to the Harbor interface.

Temporal for Workflows Support

The purpose of Model Registry API methods is to provide a set of minimalistic and sufficient routines to the service clients for getting the required information and managing models and checkpoints.

The sequence diagram for a new project creation also confirms that it is a good idea to implement this workflow using Temporal. The workflow consists of the set of sequential steps that should be executed one by one. Temporal will bring consistency, traceability and steadiness to faults to operations like this. Here is the sequential list of actions that must be applied on a new project registration:

Action Operator Description
Add a new Identity provider to the Keycloak Manually or CI/CD The Identity provider will be used to add user accounts for a new project. The groups in the Keycloak should be properly configured to distinguish user roles in the Harbor.
Add a new project in Harbor Model Registry A new project creation and configuration via Harbor API: Check for the project existence Create a new common project (public or private on demand) Create and initialize the project’s membership, i.e. add the corresponding groups Update project settings, enable signing and scanning Web hooks setup to receive the notifications Replication rules setup to share models with remote registries (optional) Project robot accounts setup and configuration (optional)
Local DB context update Model Registry Track the corresponding changes in the local Postgres database
Sending an event to the Telemetry Model Registry Compose and send the corresponding message into a Kafka topic
User Sign-in in to the Harbor admin console Manually by user At this moment users should be able to sign in into the Harbor to obtain the user secret for further work with the Harbor registry

Note

All steps related to the Model Registry service can be implemented using temporal.

Technical Implementation

Authentication and Authorization

All endpoints require JWT bearer token authentication with the following features:

  • Token extraction from Authorization header
  • JWT token validation and signature verification
  • User context extraction from token claims
  • Error handling for missing or invalid tokens

Also each API method can be executed if the external PBAC service approves the corresponding action for the current user which is authenticated using an appropriate token.

The full list of PBAC components is listed in the table below.

Component Name Description Example
Tenant Name Project/Tenant isolation identifier acme-corp, beta-client
Service Name Service unique identifier model-registry
Model access type Model classification public/private
Model Name A specific model identifier Image-2-image, cogvideo2
Model Version A specific model identifier with pattern support (wildcards) 0.1.2, 0.1.*, latest

The list of expected service actions:

Action Name Description
projects.write Allows to create a new projects and modify or remove existing
projects.read Allows to get project related information
models.write Allows to update the corresponding model data
models.read Allows to retrieve the list of models and a detailed model versions data
registries.write Allows to create new connections (endpoints) to remote registries
registries.read Allows to retrieve the information about currently registered endpoints
checkpoints.write Allows to add/modify new checkpoint records for a model version
checkpoints.read Allows to get the list of registered checkpoints for a particular model version
audit.read Allows to retrieve models audit information
cleanup.admin Allows to schedule and run cleanup actions
cleanup.read Allows to retrieve information about outdated models and versions
membership.admin Allows to manage users data and groups

Harbor Default Project Roles

Role Name Description
Project Admin Project administrators also have some management privileges, such as adding and removing members, starting a vulnerability scan.
Maintainer Maintainer has elevated permissions beyond those of ‘Developer’ including the ability to scan images, view replication jobs, and delete images and helm charts.
Developer The developer has read and write privileges for a project.
Guest Guests have read-only privilege for a specified project. They can pull and retag images, but cannot push.
Limited Guest A Limited Guest does not have full read privileges for a project. They can pull images but cannot push, and they cannot see logs or the other members of a project.
System-level Roles
System administrator The harbor system administrator has the most privileges. In addition to the privileges mentioned above, “Harbor system administrator” can also list all projects, set an ordinary user as administrator, delete users and set vulnerability scan policy for all images.
Anonymous When a user is not logged in, the user is considered as an “Anonymous” user. An anonymous user has no access to private projects and has read-only access to public projects.

Data Source for Telemetry and/or Audit

The Harbor is producing the list of various events that could be interesting to track using the Telemetry and/or Audit services abilities. There are following event types that are available:

  1. Artifact deleted
  2. Artifact pulled
  3. Artifact pushed
  4. Quota exceed
  5. Quota near threshold
  6. Replication status changed
  7. Scanning failed
  8. Scanning finished
  9. Scanning stopped
  10. Tag retention finished

The Harbor can be configured to call a webhook and provide the information about an event from the list above in JSON format. As an alternative the Model Registry service can accept an event, process it and call the corresponding Telemetry service method and pass the required data in the suitable data format.

Here is the example of JSON data for an artifact deletion event:

{
   "time":"2025-08-14T09:07:55.915933862Z",
   "host":"ub24harbor.requestcatcher.com",
   "method":"POST",
   "path":"/test",
   "headers":{
      "Accept-Encoding":[
         "gzip"
      ],
      "Content-Length":[
         "363"
      ],
      "Content-Type":[
         "application/json"
      ],
      "User-Agent":[
         "Go-http-client/1.1"
      ]
   },
   "content_length":363,
   "remote_addr":"109.252.138.131",
   "form_values":null,
   "body":" ,\n    \"resources\": [\n       \n    ]\n  },\n  \"occur_at\": 1755162475,\n  \"operator\": \"admin\",\n  \"type\": \"DELETE_ARTIFACT\"\n}",
   "raw_request":"POST /test HTTP/1.1\r\nHost: ub24harbor.requestcatcher.com\r\nAccept-Encoding: gzip\r\nContent-Length: 363\r\nContent-Type: application/json\r\nUser-Agent: Go-http-client/1.1\r\n\r\n ],\"repository\": }}"
}

The event data contains information about occurred action, client details, repository, affected artifact, etc.

Note

You can configure the prometheus server to collect harbor metrics, and use grafana to visualize your data. To begin accessing your Harbor instance’s metrics with Prometheus it is necessary to enable and setup it in the harbor.yml configuration file and set the port and path for metrics to be exposed on. The Prometheus config file has to be updated as well to scrape Harbor metrics exposed.

Database Structure

The service database structure is shown in the following diagram:

--- config: layout:elk --- erDiagram model_types { int4 id PK varchar(150) code varchar(150) name } signature_types { int4 id PK varchar(150) code varchar(150) name } validation_statuses { int4 id PK varchar(150) name } projects { uuid id PK varchar name varchar display_name varchar description bool is_public int4 harbor_id varchar tenant_id text user bool is_archived timestamptz deleted_at } repositories { uuid id PK varchar name varchar namespace varchar full_name int4 harbor_id uuid project_id FK text user bool is_archived varchar harbor_address timestamptz deleted_at } models { uuid id PK varchar name varchar display_name varchar description varchar author varchar organization varchar url int4 type_id FK uuid project_id FK uuid repository_id FK text user bool is_archived timestamptz deleted_at timestamptz creation_date varchar license _text tags } images { uuid id PK varchar digest int4 harbor_id uuid model_id FK uuid repository_id FK text user bool is_archived bool is_approved bool is_verified timestamptz deleted_at } manifests { uuid id PK varchar content varchar tag uuid image_id FK text user bool is_archived timestamptz deleted_at } metadata { uuid id PK varchar name varchar value uuid model_id FK uuid image_id FK uuid parent_id FK text user bool is_archived timestamptz deleted_at } signatures { uuid id PK varchar digest int4 type_id FK int4 harbor_id uuid image_id FK uuid repository_id FK text user bool is_archived varchar artifact_digest int4 artifact_id varchar artifact_repo timestamptz deleted_at } tags { uuid id PK varchar name int4 harbor_id uuid image_id FK text user bool is_archived timestamptz deleted_at } validation_results { uuid id PK uuid repository_id FK uuid image_id FK varchar tag varchar report_ref varchar scanner_name varchar scan_status varchar scan_severity varchar scan_summary varchar scan_type timestamptz start_time timestamptz end_time varchar xumi_status varchar xumi_summary timestamptz xumi_start_time timestamptz xumi_end_time int4 status_id FK varchar error_message varchar error_details text user bool is_archived timestamptz deleted_at } projects ||--o{ repositories : FK_repositories_projects_project_id model_types ||--o{ models : FK_models_model_types_type_id projects ||--o{ models : FK_models_projects_project_id repositories ||--o{ models : FK_models_repositories_repository_id models ||--o{ images : FK_images_models_model_id repositories ||--o{ images : FK_images_repositories_repository_id images ||--o{ manifests : FK_manifests_images_image_id images ||--o{ metadata : FK_metadata_images_image_id metadata ||--o{ metadata : FK_metadata_metadata_parent_id models ||--o{ metadata : FK_metadata_models_model_id images ||--o{ signatures : FK_signatures_images_image_id repositories ||--o{ signatures : FK_signatures_repositories_repository_id signature_types ||--o{ signatures : FK_signatures_signature_types_type_id images ||--o{ tags : FK_tags_images_image_id images ||--o{ validation_results : FK_validation_results_images_image_id repositories ||--o{ validation_results : FK_validation_results_repositories_repository_id validation_statuses ||--o{ validation_results : FK_validation_results_validation_statuses_status_id
Database Diagram in SQL DDL
-- DROP SCHEMA modelregistry;

CREATE SCHEMA modelregistry
AUTHORIZATION postgres;

-- ============================================================
-- model_types
-- ============================================================

CREATE TABLE modelregistry.model_types (
    id    int4         NOT NULL,
    code  varchar(150) NOT NULL,
    name  varchar(150) NOT NULL,
    CONSTRAINT "PK_model_types"
        PRIMARY KEY (id)
);

CREATE UNIQUE INDEX "IX_model_types_name"
    ON modelregistry.model_types (name);

CREATE UNIQUE INDEX ux_model_type_name_and_code
    ON modelregistry.model_types (name, code);

-- ============================================================
-- signature_types
-- ============================================================

CREATE TABLE modelregistry.signature_types (
    id    int4         NOT NULL,
    code  varchar(150) NOT NULL,
    name  varchar(150) NOT NULL,
    CONSTRAINT "PK_signature_types"
        PRIMARY KEY (id)
);

CREATE UNIQUE INDEX "IX_signature_types_name"
    ON modelregistry.signature_types (name);

CREATE UNIQUE INDEX ux_signature_type_name_and_code
    ON modelregistry.signature_types (name, code);


-- ============================================================
-- validation_statuses
-- ============================================================

CREATE TABLE modelregistry.validation_statuses (
    id    int4         NOT NULL,
    name  varchar(150) NOT NULL,
    CONSTRAINT "PK_validation_statuses"
        PRIMARY KEY (id)
);

CREATE UNIQUE INDEX ux_image_status_name
    ON modelregistry.validation_statuses (name);

-- ============================================================
-- projects
-- ============================================================

CREATE TABLE modelregistry.projects (
    id           uuid        NOT NULL,
    name         varchar     NOT NULL,
    display_name varchar     NOT NULL,
    description  varchar     NULL,
    is_public    bool        NOT NULL DEFAULT false,
    harbor_id    int4        NULL,
    tenant_id    varchar     NOT NULL,
    "user"       text        NULL,
    is_archived  bool        NOT NULL,
    deleted_at   timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_projects"
        PRIMARY KEY (id),
);

-- ============================================================
-- repositories
-- ============================================================

CREATE TABLE modelregistry.repositories (
    id              uuid        NOT NULL,
    name            varchar     NOT NULL,
    namespace       varchar     NOT NULL,
    full_name       varchar     NOT NULL,
    harbor_id       int4        NULL,
    project_id      uuid        NOT NULL,
    "user"          text        NULL,
    is_archived     bool        NOT NULL,
    harbor_address  varchar     NOT NULL DEFAULT '',
    deleted_at      timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_repositories"
        PRIMARY KEY (id),
    CONSTRAINT "FK_repositories_projects_project_id"
        FOREIGN KEY (project_id)
        REFERENCES modelregistry.projects (id)
        ON DELETE CASCADE
);

CREATE INDEX "IX_repositories_project_id"
    ON modelregistry.repositories (project_id);

CREATE UNIQUE INDEX ux_repository_name_project
    ON modelregistry.repositories (
        name,
        namespace,
        project_id,
        is_archived,
        deleted_at
    );

-- ============================================================
-- models
-- ============================================================

CREATE TABLE modelregistry.models (
    id             uuid        NOT NULL,
    name           varchar     NOT NULL,
    display_name   varchar     NULL,
    description    varchar     NULL,
    author         varchar     NULL,
    organization   varchar     NULL,
    url            varchar     NULL,
    type_id        int4        NOT NULL DEFAULT 0,
    project_id     uuid        NOT NULL,
    repository_id  uuid        NOT NULL,
    "user"         text        NULL,
    is_archived    bool        NOT NULL,
    deleted_at     timestamptz NOT NULL DEFAULT '-infinity',
    creation_date  timestamptz NULL,
    license        varchar     NULL,
    tags           _text       NULL,
    CONSTRAINT "PK_models"
        PRIMARY KEY (id),
    CONSTRAINT "FK_models_model_types_type_id"
        FOREIGN KEY (type_id)
        REFERENCES modelregistry.model_types (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_models_projects_project_id"
        FOREIGN KEY (project_id)
        REFERENCES modelregistry.projects (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_models_repositories_repository_id"
        FOREIGN KEY (repository_id)
        REFERENCES modelregistry.repositories (id)
        ON DELETE CASCADE
);

-- ============================================================
-- images
-- ============================================================

CREATE TABLE modelregistry.images (
    id            uuid        NOT NULL,
    digest        varchar     NOT NULL,
    harbor_id     int4        NULL,
    model_id      uuid        NOT NULL,
    repository_id uuid        NOT NULL,
    "user"        text        NULL,
    is_archived   bool        NOT NULL,
    is_approved   bool        NOT NULL DEFAULT false,
    is_verified   bool        NOT NULL DEFAULT false,
    deleted_at    timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_images"
        PRIMARY KEY (id),
    CONSTRAINT "FK_images_models_model_id"
        FOREIGN KEY (model_id)
        REFERENCES modelregistry.models (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_images_repositories_repository_id"
        FOREIGN KEY (repository_id)
        REFERENCES modelregistry.repositories (id)
        ON DELETE CASCADE
);

-- ============================================================
-- manifests
-- ============================================================

CREATE TABLE modelregistry.manifests (
    id          uuid        NOT NULL,
    content     varchar     NOT NULL,
    tag         varchar     NOT NULL,
    image_id    uuid        NOT NULL,
    "user"      text        NULL,
    is_archived bool        NOT NULL,
    deleted_at  timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_manifests"
        PRIMARY KEY (id),
    CONSTRAINT "FK_manifests_images_image_id"
        FOREIGN KEY (image_id)
        REFERENCES modelregistry.images (id)
        ON DELETE CASCADE
);

-- ============================================================
-- metadata
-- ============================================================

CREATE TABLE modelregistry.metadata (
    id          uuid        NOT NULL,
    name        varchar     NOT NULL,
    value       varchar     NULL,
    model_id    uuid        NOT NULL,
    image_id    uuid        NULL,
    parent_id   uuid        NULL,
    "user"      text        NULL,
    is_archived bool        NOT NULL,
    deleted_at  timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_metadata"
        PRIMARY KEY (id),
    CONSTRAINT "FK_metadata_images_image_id"
        FOREIGN KEY (image_id)
        REFERENCES modelregistry.images (id),
    CONSTRAINT "FK_metadata_metadata_parent_id"
        FOREIGN KEY (parent_id)
        REFERENCES modelregistry.metadata (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_metadata_models_model_id"
        FOREIGN KEY (model_id)
        REFERENCES modelregistry.models (id)
        ON DELETE CASCADE
);

-- ============================================================
-- signatures
-- ============================================================

CREATE TABLE modelregistry.signatures (
    id              uuid        NOT NULL,
    digest          varchar     NOT NULL,
    type_id         int4        NOT NULL,
    harbor_id       int4        NULL,
    image_id        uuid        NULL,
    repository_id   uuid        NOT NULL,
    "user"          text        NULL,
    is_archived     bool        NOT NULL,
    artifact_digest varchar     NULL,
    artifact_id     int4        NULL,
    artifact_repo   varchar     NULL,
    deleted_at      timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_signatures"
        PRIMARY KEY (id),
    CONSTRAINT "FK_signatures_images_image_id"
        FOREIGN KEY (image_id)
        REFERENCES modelregistry.images (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_signatures_repositories_repository_id"
        FOREIGN KEY (repository_id)
        REFERENCES modelregistry.repositories (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_signatures_signature_types_type_id"
        FOREIGN KEY (type_id)
        REFERENCES modelregistry.signature_types (id)
        ON DELETE CASCADE
);

-- ============================================================
-- tags
-- ============================================================

CREATE TABLE modelregistry.tags (
    id          uuid        NOT NULL,
    name        varchar     NOT NULL,
    harbor_id   int4        NULL,
    image_id    uuid        NOT NULL,
    "user"      text        NULL,
    is_archived bool        NOT NULL,
    deleted_at  timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_tags"
        PRIMARY KEY (id),
    CONSTRAINT "FK_tags_images_image_id"
        FOREIGN KEY (image_id)
        REFERENCES modelregistry.images (id)
        ON DELETE CASCADE
);

-- ============================================================
-- validation_results
-- ============================================================

CREATE TABLE modelregistry.validation_results (
    id               uuid        NOT NULL,
    repository_id    uuid        NOT NULL,
    image_id         uuid        NULL,
    tag              varchar     NOT NULL,
    report_ref       varchar     NULL,
    scanner_name     varchar     NULL,
    scan_status      varchar     NULL,
    scan_severity    varchar     NULL,
    scan_summary     varchar     NULL,
    scan_type        varchar     NOT NULL DEFAULT '',
    start_time       timestamptz NULL,
    end_time         timestamptz NULL,
    xumi_status      varchar     NULL,
    xumi_summary     varchar     NULL,
    xumi_start_time  timestamptz NULL,
    xumi_end_time    timestamptz NULL,
    status_id        int4        NOT NULL DEFAULT 0,
    error_message    varchar     NULL,
    error_details    varchar     NULL,
    "user"           text        NULL,
    is_archived      bool        NOT NULL,
    deleted_at       timestamptz NOT NULL DEFAULT '-infinity',
    CONSTRAINT "PK_validation_results"
        PRIMARY KEY (id),
    CONSTRAINT "FK_validation_results_images_image_id"
        FOREIGN KEY (image_id)
        REFERENCES modelregistry.images (id)
        ON DELETE SET NULL,
    CONSTRAINT "FK_validation_results_repositories_repository_id"
        FOREIGN KEY (repository_id)
        REFERENCES modelregistry.repositories (id)
        ON DELETE CASCADE,
    CONSTRAINT "FK_validation_results_validation_statuses_status_id"
        FOREIGN KEY (status_id)
        REFERENCES modelregistry.validation_statuses (id)
        ON DELETE CASCADE
);