// V2 of the registry API. The wire surface is forked end-to-end so v2 callers never see legacy
// name-shaped types: every RPC has its own v2 request/response and the v2 ContainerImage drops
// `name` entirely in favour of (workspace, extractor, tag) identity. Leaf types whose shape
// doesn't change between versions (FileExtractionInput, FileOutputFormat, ContainerImageStatus,
// TimestampMetadata) are imported from v1 rather than re-declared.
//
// V2 differs from v1 in that:
//   - Images are keyed by (workspace, extractor, tag) instead of (workspace, name, tag).
//     Extractor identity replaces the user-supplied image name as the stable handle, so the
//     v2 ContainerImage type drops `name` entirely.
//   - The OCI repository path is {workspaceUuid}/{extractorUuid}:{tag}, replacing the legacy
//     {workspaceUuid}/{name}:{tag} layout.
//   - CreateImage requires extractor_rid, inputs, file_output_format so the registry has a
//     complete execution contract for the image, rather than relying on a separately persisted
//     record on the extractor that can drift from the image it points at.
//   - SearchFilter has no NameFilter (v2 images have no name to filter on); only TagFilter,
//     StatusFilter, and AndFilter are exposed.
//
// On the Java side, only CreateImage has its own service method (createImageV2): the OCI key,
// DB columns, and required metadata genuinely differ from v1. GetImage / DeleteImage /
// SearchImages share the existing v1 service methods on RegistryService; the v2 resource calls
// them and converts the v1 response into v2 wire types at the resource layer. v1 CreateImage
// remains in place until existing clients migrate.
//
// V1 CreateImage stays in place until existing clients migrate.
syntax = "proto3";

package nominal.registry.v2;

import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";
import "nominal/protos/gen/v1/alias.proto";
import "nominal/protos/registry/v1/registry.proto";

option java_multiple_files = true;
option java_package = "io.nominal.registry.v2";

// Container image registered through the V2 (extractor-linked) registry flow. Distinct from
// nominal.registry.v1.ContainerImage because v2 images carry no user-supplied `name` — extractor
// identity replaces it as the stable handle. Leaf types (FileExtractionInput, FileOutputFormat,
// TimestampMetadata, ContainerImageStatus) are reused from v1 because their on-the-wire shape is
// the same; only the composition differs.
message ContainerImage {
  // Same RID space as nominal.registry.v1.ContainerImage — both surfaces register against the
  // same underlying image table.
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  // User-supplied tag, unique within (workspace, extractor).
  string tag = 2;
  // Size of the stored image in bytes once the image reaches READY.
  optional int64 size_bytes = 3;
  // Current lifecycle status (PENDING while pushing, READY after a successful push, FAILED otherwise).
  nominal.registry.v1.ContainerImageStatus status = 4;
  // When the image record was created.
  google.protobuf.Timestamp created_at = 5;
  // The containerized extractor this image is built for.
  string extractor_rid = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.ContainerizedExtractorRid"}];
  // The input files this extractor requires, mapped to environment variables that store the path
  // to each file.
  repeated nominal.registry.v1.FileExtractionInput inputs = 7;
  // Parameters the extractor accepts.
  repeated nominal.registry.v1.FileExtractionParameter parameters = 8;
  // Format of the output file produced by the extractor.
  nominal.registry.v1.FileOutputFormat file_output_format = 9;
  // Metadata about the intermediate parquet this extractor will produce. If not set, timestamp
  // metadata must be provided at ingest time.
  optional nominal.registry.v1.TimestampMetadata default_timestamp_metadata = 10;
}

message CreateImageRequest {
  // Workspace that owns the image. Must match the extractor's workspace.
  string workspace_rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // User-supplied tag (e.g. "latest", "v1"). Unique within (workspace, extractor).
  string tag = 2 [(buf.validate.field).required = true];
  // Full object-storage path ("s3://bucket/key") returned by the existing multipart upload
  // flow (UploadService). Must reference an object in the configured upload bucket.
  string object_path = 3 [(buf.validate.field).required = true];
  // The containerized extractor this image is built for. Must exist in the same workspace.
  string extractor_rid = 4 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.ContainerizedExtractorRid"}
  ];
  // The input files this extractor requires, mapped to environment variables that store the
  // path to each file. At least one input must be declared.
  repeated nominal.registry.v1.FileExtractionInput inputs = 5 [(buf.validate.field).repeated.min_items = 1];
  // Parameters the extractor accepts.
  repeated nominal.registry.v1.FileExtractionParameter parameters = 6;
  // Format of the output file produced by the extractor.
  nominal.registry.v1.FileOutputFormat file_output_format = 7 [(buf.validate.field).enum = {
    defined_only: true
    not_in: [0]
  }];
  // Metadata about the intermediate parquet this extractor will produce. If not set,
  // timestamp metadata must be provided at ingest time.
  optional nominal.registry.v1.TimestampMetadata default_timestamp_metadata = 8;
}

message CreateImageResponse {
  ContainerImage image = 1;
}

message GetImageRequest {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  string workspace_rid = 2 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
}

message GetImageResponse {
  ContainerImage image = 1;
}

message DeleteImageRequest {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  string workspace_rid = 2 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
}

message DeleteImageResponse {}

// V2 search filter types. TagFilter / StatusFilter are shape-identical to their v1 counterparts
// but redefined here so the v2 SearchFilter oneof is self-contained — v2 deliberately omits
// NameFilter (v2 images have no user-supplied name) and may add ExtractorFilter in a follow-up.
message TagFilter {
  string tag = 1 [(buf.validate.field).required = true];
}

message StatusFilter {
  nominal.registry.v1.ContainerImageStatus status = 1 [(buf.validate.field).required = true];
}

message AndFilter {
  repeated SearchFilter clauses = 1;
}

message SearchFilter {
  oneof filter {
    option (buf.validate.oneof) = {required: true};

    TagFilter tag = 1;
    StatusFilter status = 2;
    AndFilter and = 3;
  }
}

message SearchImagesRequest {
  string workspace_rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // Optional composable filter. Omit to list all images in the workspace.
  optional SearchFilter filter = 2;
  optional int32 page_size = 3 [
    (buf.validate.field).int32.gte = 1,
    (buf.validate.field).int32.lte = 1000
  ];
  optional string next_page_token = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

message SearchImagesResponse {
  repeated ContainerImage images = 1;
  optional string next_page_token = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

// Speaks gRPC/proto alongside the existing nominal.registry.v1.RegistryService. The wire surface
// is forked entirely so v2 callers never see legacy `name`-shaped types, but the read endpoints
// (GetImage / DeleteImage / SearchImages) delegate to the same shared Java service methods that
// back v1 — only CreateImage has a v2-specific service path (different OCI key, different DB
// fields). See file-level comment.
service RegistryService {
  // Register a new container image from a previously uploaded tarball and attach it to the
  // referenced containerized extractor. The client should first upload the tarball via the
  // existing UploadService multipart flow, then call this endpoint with the object key to
  // create the image record and start the push.
  rpc CreateImage(CreateImageRequest) returns (CreateImageResponse) {}

  // Get the current state of a container image.
  rpc GetImage(GetImageRequest) returns (GetImageResponse) {}

  // Delete a container image. Fails if any containerized extractor references it.
  rpc DeleteImage(DeleteImageRequest) returns (DeleteImageResponse) {}

  // List container images in a workspace, optionally filtered by tag / status.
  rpc SearchImages(SearchImagesRequest) returns (SearchImagesResponse) {}
}
