diff --git a/proto/tea/v1/insights.proto b/proto/tea/v1/insights.proto new file mode 100644 index 0000000..160f387 --- /dev/null +++ b/proto/tea/v1/insights.proto @@ -0,0 +1,791 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.proto"; +import "tea/v1/artifact.proto"; + +option go_package = "github.com/CycloneDX/transparency-exchange-api/gen/go/tea/v1;teav1"; +option java_package = "org.cyclonedx.tea.v1"; +option java_multiple_files = true; +option csharp_namespace = "CycloneDX.Tea.V1"; + +// ============================================================================ +// Insights Service +// ============================================================================ + +// InsightsService provides query and analytics capabilities for TEA data. +// This enables "limited transparency" queries without requiring full artifact +// downloads and processing by the consumer. +// +// The service supports: +// - CEL (Common Expression Language) queries +// - Pre-built queries for common use cases +// - Vulnerability analysis +// - Component dependency analysis +// +// This service is an optional part of the TEA specification. +// Authentication is typically required for insights queries. +service InsightsService { + // ========================================================================== + // CEL Query Operations + // ========================================================================== + + // Query executes a CEL expression against TEA data. + // CEL provides a type-safe, sandboxed query language. + // See: https://github.com/google/cel-spec + rpc Query(QueryRequest) returns (QueryResponse) { + option (google.api.http) = { + post: "/v1/insights/query" + body: "*" + }; + } + + // ValidateQuery validates a CEL expression without executing it. + rpc ValidateQuery(ValidateQueryRequest) returns (ValidateQueryResponse) { + option (google.api.http) = { + post: "/v1/insights/query/validate" + body: "*" + }; + } + + // ========================================================================== + // Pre-built Queries + // ========================================================================== + + // GetVulnerabilitySummary returns vulnerability information for a release. + rpc GetVulnerabilitySummary(GetVulnerabilitySummaryRequest) returns (VulnerabilitySummary) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/vulnerabilities" + }; + } + + // GetComponentDependencies returns component dependencies for a release. + rpc GetComponentDependencies(GetComponentDependenciesRequest) returns (ComponentDependencies) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/dependencies" + }; + } + + // FindAffectedReleases finds releases affected by a specific component. + rpc FindAffectedReleases(FindAffectedReleasesRequest) returns (FindAffectedReleasesResponse) { + option (google.api.http) = { + get: "/v1/insights/affected" + }; + } + + // GetLicenseSummary returns license information for a release. + rpc GetLicenseSummary(GetLicenseSummaryRequest) returns (LicenseSummary) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/licenses" + }; + } + + // GetCryptoInventory returns cryptographic assets for a release. + // Based on CBOM (Cryptographic Bill of Materials). + rpc GetCryptoInventory(GetCryptoInventoryRequest) returns (CryptoInventory) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/crypto" + }; + } + + // ========================================================================== + // Analytics + // ========================================================================== + + // GetReleaseMetrics returns analytics for a release. + rpc GetReleaseMetrics(GetReleaseMetricsRequest) returns (ReleaseMetrics) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/metrics" + }; + } + + // CompareSBOMs compares two SBOMs for drift detection. + rpc CompareSBOMs(CompareSBOMsRequest) returns (SBOMComparison) { + option (google.api.http) = { + post: "/v1/insights/compare/sbom" + body: "*" + }; + } +} + +// ============================================================================ +// CEL Query Messages +// ============================================================================ + +// QueryRequest contains a CEL query to execute. +message QueryRequest { + // CEL expression to evaluate. + // The expression has access to TEA data objects (products, components, + // releases, artifacts, sboms, vex, etc.). + // + // Example expressions: + // - components.exists(c, c.purl == "pkg:maven/org.apache.logging.log4j/log4j-core") + // - sbom.components.filter(c, c.vulnerabilities.size() > 0) + // - releases.filter(r, r.artifacts.exists(a, a.type == "VULNERABILITIES")) + string expression = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 10000, + (buf.validate.field).required = true + ]; + + // Scope of the query (what data to query against). + QueryScope scope = 2; + + // Variables to bind in the expression. + map variables = 3; + + // Maximum execution time (server may enforce limits). + int32 timeout_seconds = 4 [json_name = "timeoutSeconds"]; + + // Pagination for results. + PageRequest pagination = 5; + + // Output format. + QueryOutputFormat output_format = 6 [json_name = "outputFormat"]; +} + +// QueryScope defines what data the query operates on. +message QueryScope { + // Query against a specific release. + optional string release_uuid = 1 [json_name = "releaseUuid"]; + + // Query against a specific product. + optional string product_uuid = 2 [json_name = "productUuid"]; + + // Query against all data (requires special permission). + bool global = 3; + + // Artifact types to include in scope. + repeated ArtifactType artifact_types = 4 [json_name = "artifactTypes"]; + + // Date range for temporal queries. + DateRange date_range = 5 [json_name = "dateRange"]; +} + +// QueryOutputFormat specifies how to format query results. +enum QueryOutputFormat { + // Unspecified - server default (JSON). + QUERY_OUTPUT_FORMAT_UNSPECIFIED = 0; + + // JSON output. + QUERY_OUTPUT_FORMAT_JSON = 1; + + // Protocol Buffer Any. + QUERY_OUTPUT_FORMAT_PROTO = 2; + + // CSV (for tabular results). + QUERY_OUTPUT_FORMAT_CSV = 3; +} + +// QueryResponse contains the results of a CEL query. +message QueryResponse { + // Query result as JSON (when output_format is JSON). + google.protobuf.Struct result = 1; + + // Query result as Any (when output_format is PROTO). + google.protobuf.Any result_proto = 2 [json_name = "resultProto"]; + + // Query result as CSV (when output_format is CSV). + string result_csv = 3 [json_name = "resultCsv"]; + + // Number of results. + int64 result_count = 4 [json_name = "resultCount"]; + + // Execution time in milliseconds. + int64 execution_time_ms = 5 [json_name = "executionTimeMs"]; + + // Pagination for continued results. + PageResponse pagination = 6; + + // Warnings during execution. + repeated string warnings = 7; +} + +// ValidateQueryRequest validates a CEL expression. +message ValidateQueryRequest { + // CEL expression to validate. + string expression = 1 [(buf.validate.field).required = true]; +} + +// ValidateQueryResponse contains validation results. +message ValidateQueryResponse { + // Whether the expression is valid. + bool valid = 1; + + // Validation errors (if any). + repeated QueryValidationError errors = 2; + + // Inferred return type. + string return_type = 3 [json_name = "returnType"]; + + // Estimated complexity score. + int32 complexity_score = 4 [json_name = "complexityScore"]; +} + +// QueryValidationError describes a CEL validation error. +message QueryValidationError { + // Error message. + string message = 1; + + // Position in expression. + int32 position = 2; + + // Line number. + int32 line = 3; + + // Column number. + int32 column = 4; +} + +// ============================================================================ +// Vulnerability Messages +// ============================================================================ + +// GetVulnerabilitySummaryRequest requests vulnerability summary. +message GetVulnerabilitySummaryRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; + + // Include VEX analysis (not just raw vulnerabilities). + bool include_vex_analysis = 2 [json_name = "includeVexAnalysis"]; + + // Minimum severity to include. + VulnerabilitySeverity min_severity = 3 [json_name = "minSeverity"]; +} + +// VulnerabilitySummary contains vulnerability information. +message VulnerabilitySummary { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Total vulnerabilities found. + int32 total_count = 2 [json_name = "totalCount"]; + + // Counts by severity. + VulnerabilityCounts by_severity = 3 [json_name = "bySeverity"]; + + // Counts by status (from VEX). + VexStatusCounts by_vex_status = 4 [json_name = "byVexStatus"]; + + // Top vulnerabilities (most severe). + repeated VulnerabilityInfo top_vulnerabilities = 5 [json_name = "topVulnerabilities"]; + + // When the analysis was performed. + google.protobuf.Timestamp analyzed_at = 6 [json_name = "analyzedAt"]; + + // Artifacts used for this analysis. + repeated string source_artifact_uuids = 7 [json_name = "sourceArtifactUuids"]; +} + +// VulnerabilitySeverity levels. +enum VulnerabilitySeverity { + // Severity could not be determined. + VULNERABILITY_SEVERITY_UNSPECIFIED = 0; + // No vulnerability is present. + VULNERABILITY_SEVERITY_NONE = 1; + // Vulnerability has low impact. + VULNERABILITY_SEVERITY_LOW = 2; + // Vulnerability has medium impact. + VULNERABILITY_SEVERITY_MEDIUM = 3; + // Vulnerability has high impact. + VULNERABILITY_SEVERITY_HIGH = 4; + // Vulnerability has critical impact. + VULNERABILITY_SEVERITY_CRITICAL = 5; +} + +// VulnerabilityCounts by severity. +message VulnerabilityCounts { + // Number of critical vulnerabilities. + int32 critical = 1; + // Number of high-severity vulnerabilities. + int32 high = 2; + // Number of medium-severity vulnerabilities. + int32 medium = 3; + // Number of low-severity vulnerabilities. + int32 low = 4; + // Number of explicitly non-applicable vulnerabilities. + int32 none = 5; + // Number of vulnerabilities with unknown severity. + int32 unknown = 6; +} + +// VexStatusCounts by VEX status. +message VexStatusCounts { + // Number of affected findings. + int32 affected = 1; + // Number of findings marked not affected. + int32 not_affected = 2; + // Number of findings marked fixed. + int32 fixed = 3; + // Number of findings still under investigation. + int32 under_investigation = 4 [json_name = "underInvestigation"]; +} + +// VulnerabilityInfo contains details about a vulnerability. +message VulnerabilityInfo { + // CVE or other vulnerability ID. + string id = 1; + + // Severity level. + VulnerabilitySeverity severity = 2; + + // CVSS score (if available). + optional float cvss_score = 3 [json_name = "cvssScore"]; + + // Brief description. + string description = 4; + + // Affected component. + Identifier affected_component = 5 [json_name = "affectedComponent"]; + + // VEX status (if VEX is available). + optional VexStatus vex_status = 6 [json_name = "vexStatus"]; + + // VEX justification. + string vex_justification = 7 [json_name = "vexJustification"]; +} + +// VexStatus from VEX documents. +enum VexStatus { + // No VEX status is available. + VEX_STATUS_UNSPECIFIED = 0; + // The product is affected by the vulnerability. + VEX_STATUS_AFFECTED = 1; + // The product is not affected by the vulnerability. + VEX_STATUS_NOT_AFFECTED = 2; + // The vulnerability has been fixed. + VEX_STATUS_FIXED = 3; + // Impact analysis is still in progress. + VEX_STATUS_UNDER_INVESTIGATION = 4; +} + +// ============================================================================ +// Dependency Messages +// ============================================================================ + +// GetComponentDependenciesRequest requests dependency tree. +message GetComponentDependenciesRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; + + // Maximum depth to traverse (0 = unlimited). + int32 max_depth = 2 [json_name = "maxDepth"]; + + // Include transitive dependencies. + bool include_transitive = 3 [json_name = "includeTransitive"]; +} + +// ComponentDependencies contains the dependency tree. +message ComponentDependencies { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Root component. + DependencyNode root = 2; + + // Total dependency count (including transitive). + int32 total_count = 3 [json_name = "totalCount"]; + + // Direct dependency count. + int32 direct_count = 4 [json_name = "directCount"]; + + // Transitive dependency count. + int32 transitive_count = 5 [json_name = "transitiveCount"]; +} + +// DependencyNode represents a component in the dependency tree. +message DependencyNode { + // Component identifier. + Identifier identifier = 1; + + // Component name. + string name = 2; + + // Component version. + string version = 3; + + // Dependency scope (runtime, dev, test, etc.). + string scope = 4; + + // Direct dependencies. + repeated DependencyNode dependencies = 5; + + // Depth in the tree. + int32 depth = 6; +} + +// FindAffectedReleasesRequest finds releases using a component. +message FindAffectedReleasesRequest { + // Identifier of the component to search for. + Identifier identifier = 1 [(buf.validate.field).required = true]; + + // Version range (VERS format). + string version_range = 2 [json_name = "versionRange"]; + + // Pagination. + PageRequest pagination = 3; +} + +// FindAffectedReleasesResponse contains matching releases. +message FindAffectedReleasesResponse { + // Releases that use the specified component. + repeated AffectedRelease releases = 1; + + // Pagination. + PageResponse pagination = 2; +} + +// AffectedRelease contains info about an affected release. +message AffectedRelease { + // Release UUID. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Release version. + string version = 2; + + // Product name (if available). + string product_name = 3 [json_name = "productName"]; + + // How the component is used (direct/transitive). + string dependency_type = 4 [json_name = "dependencyType"]; + + // Specific version of the component used. + string component_version = 5 [json_name = "componentVersion"]; +} + +// ============================================================================ +// License Messages +// ============================================================================ + +// GetLicenseSummaryRequest requests license summary. +message GetLicenseSummaryRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; +} + +// LicenseSummary contains license information. +message LicenseSummary { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Total components analyzed. + int32 total_components = 2 [json_name = "totalComponents"]; + + // Components with identified licenses. + int32 with_license = 3 [json_name = "withLicense"]; + + // Components without identified licenses. + int32 without_license = 4 [json_name = "withoutLicense"]; + + // License distribution. + repeated LicenseCount licenses = 5; + + // License conflicts (if any). + repeated LicenseConflict conflicts = 6; +} + +// LicenseCount represents a license and its usage count. +message LicenseCount { + // SPDX license ID. + string spdx_id = 1 [json_name = "spdxId"]; + + // License name. + string name = 2; + + // Number of components using this license. + int32 count = 3; + + // License category (permissive, copyleft, proprietary). + string category = 4; +} + +// LicenseConflict describes a potential license conflict. +message LicenseConflict { + // First license. + string license1 = 1; + + // Second license. + string license2 = 2; + + // Description of the conflict. + string description = 3; + + // Severity (info, warning, error). + string severity = 4; +} + +// ============================================================================ +// Crypto Messages +// ============================================================================ + +// GetCryptoInventoryRequest requests cryptographic inventory. +message GetCryptoInventoryRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; +} + +// CryptoInventory contains cryptographic assets. +message CryptoInventory { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Cryptographic algorithms in use. + repeated CryptoAlgorithm algorithms = 2; + + // Certificates found. + repeated CryptoCertificate certificates = 3; + + // Quantum readiness assessment. + QuantumReadiness quantum_readiness = 4 [json_name = "quantumReadiness"]; +} + +// CryptoAlgorithm describes a cryptographic algorithm. +message CryptoAlgorithm { + // Algorithm name (e.g., "AES-256-GCM", "RSA-2048"). + string name = 1; + + // Algorithm type (symmetric, asymmetric, hash, etc.). + string type = 2; + + // Usage (encryption, signing, hashing, key-exchange). + string usage = 3; + + // Key size (if applicable). + int32 key_size = 4 [json_name = "keySize"]; + + // Is quantum-safe. + bool quantum_safe = 5 [json_name = "quantumSafe"]; + + // Components using this algorithm. + repeated string components = 6; +} + +// CryptoCertificate describes a certificate. +message CryptoCertificate { + // Certificate subject. + string subject = 1; + + // Certificate issuer. + string issuer = 2; + + // Expiration date. + google.protobuf.Timestamp expires = 3; + + // Public key algorithm. + string algorithm = 4; + + // Key size. + int32 key_size = 5 [json_name = "keySize"]; +} + +// QuantumReadiness assesses post-quantum cryptography readiness. +message QuantumReadiness { + // Overall readiness score (0-100). + int32 score = 1; + + // Number of quantum-vulnerable algorithms. + int32 vulnerable_count = 2 [json_name = "vulnerableCount"]; + + // Number of quantum-safe algorithms. + int32 safe_count = 3 [json_name = "safeCount"]; + + // Recommendations. + repeated string recommendations = 4; +} + +// ============================================================================ +// Metrics Messages +// ============================================================================ + +// GetReleaseMetricsRequest requests release metrics. +message GetReleaseMetricsRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; +} + +// ReleaseMetrics contains analytics for a release. +message ReleaseMetrics { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Component metrics. + ComponentMetrics components = 2; + + // Artifact metrics. + ArtifactMetrics artifacts = 3; + + // Collection metrics. + CollectionMetrics collection = 4; +} + +// ComponentMetrics contains component-related metrics. +message ComponentMetrics { + // Total components. + int32 total = 1; + + // Direct dependencies. + int32 direct = 2; + + // Transitive dependencies. + int32 transitive = 3; + + // By type (library, framework, etc.). + map by_type = 4 [json_name = "byType"]; + + // By ecosystem (maven, npm, etc.). + map by_ecosystem = 5 [json_name = "byEcosystem"]; +} + +// ArtifactMetrics contains artifact-related metrics. +message ArtifactMetrics { + // Total artifacts. + int32 total = 1; + + // By type. + map by_type = 2 [json_name = "byType"]; + + // Total size in bytes. + int64 total_size_bytes = 3 [json_name = "totalSizeBytes"]; +} + +// CollectionMetrics contains collection-related metrics. +message CollectionMetrics { + // Collection version. + int32 version = 1; + + // Number of updates. + int32 update_count = 2 [json_name = "updateCount"]; + + // Last update timestamp. + google.protobuf.Timestamp last_updated = 3 [json_name = "lastUpdated"]; + + // Is signed. + bool is_signed = 4 [json_name = "isSigned"]; +} + +// ============================================================================ +// SBOM Comparison Messages +// ============================================================================ + +// CompareSBOMsRequest compares two SBOMs. +message CompareSBOMsRequest { + // First SBOM (base). + oneof base { + // Base release UUID. + string base_release_uuid = 1; + // Base artifact UUID. + string base_artifact_uuid = 2; + } + + // Second SBOM (target). + oneof target { + // Target release UUID. + string target_release_uuid = 3; + // Target artifact UUID. + string target_artifact_uuid = 4; + } + + // Include detailed component changes. + bool include_details = 5 [json_name = "includeDetails"]; +} + +// SBOMComparison contains comparison results. +message SBOMComparison { + // Components added in target. + repeated ComponentChange added = 1; + + // Components removed in target. + repeated ComponentChange removed = 2; + + // Components with version changes. + repeated ComponentVersionChange updated = 3; + + // Components unchanged. + int32 unchanged_count = 4 [json_name = "unchangedCount"]; + + // Summary statistics. + ComparisonSummary summary = 5; +} + +// ComponentChange describes an added or removed component. +message ComponentChange { + // Component identifier. + Identifier identifier = 1; + + // Component name. + string name = 2; + + // Component version. + string version = 3; +} + +// ComponentVersionChange describes a version change. +message ComponentVersionChange { + // Component identifier. + Identifier identifier = 1; + + // Component name. + string name = 2; + + // Previous version. + string old_version = 3 [json_name = "oldVersion"]; + + // New version. + string new_version = 4 [json_name = "newVersion"]; + + // Is upgrade (true) or downgrade (false). + bool is_upgrade = 5 [json_name = "isUpgrade"]; +} + +// ComparisonSummary contains comparison statistics. +message ComparisonSummary { + // Components in base SBOM. + int32 base_count = 1 [json_name = "baseCount"]; + + // Components in target SBOM. + int32 target_count = 2 [json_name = "targetCount"]; + + // Components added. + int32 added_count = 3 [json_name = "addedCount"]; + + // Components removed. + int32 removed_count = 4 [json_name = "removedCount"]; + + // Components updated. + int32 updated_count = 5 [json_name = "updatedCount"]; + + // Components unchanged. + int32 unchanged_count = 6 [json_name = "unchangedCount"]; + + // Similarity percentage. + float similarity_percent = 7 [json_name = "similarityPercent"]; +} diff --git a/proto/tea/v1/publisher.proto b/proto/tea/v1/publisher.proto new file mode 100644 index 0000000..93ace40 --- /dev/null +++ b/proto/tea/v1/publisher.proto @@ -0,0 +1,792 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.proto"; +import "tea/v1/product.proto"; +import "tea/v1/component.proto"; +import "tea/v1/collection.proto"; +import "tea/v1/artifact.proto"; + +option go_package = "github.com/CycloneDX/transparency-exchange-api/gen/go/tea/v1;teav1"; +option java_package = "org.cyclonedx.tea.v1"; +option java_multiple_files = true; +option csharp_namespace = "CycloneDX.Tea.V1"; + +// ============================================================================ +// Publisher Service +// ============================================================================ + +// PublisherService provides write access for artifact publishers to manage +// transparency artifacts in the TEA server. +// +// All endpoints require authentication and appropriate authorization. +// The exact auth mechanism and scope vocabulary are deployment-specific, but +// implementations MUST reject unauthorized writes. +// +// This service implements the TEA Publisher API specification. +// Note: The Publisher API is a recommended (optional) part of the TEA spec. +// Implementations MAY expose only a subset of these RPCs, but unsupported +// operations MUST fail explicitly rather than partially succeeding. +service PublisherService { + // ========================================================================== + // Product Management + // ========================================================================== + + // CreateProduct creates a new product. + rpc CreateProduct(CreateProductRequest) returns (Product) { + option (google.api.http) = { + post: "/v1/publisher/products" + body: "*" + }; + } + + // UpdateProduct updates an existing product. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateProduct(UpdateProductRequest) returns (Product) { + option (google.api.http) = { + put: "/v1/publisher/products/{uuid}" + body: "*" + }; + } + + // DeleteProduct removes a product and optionally its releases. + // Implementations SHOULD fail with FAILED_PRECONDITION when cascade=false and + // dependent releases still exist. + rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse) { + option (google.api.http) = { + delete: "/v1/publisher/products/{uuid}" + }; + } + + // CreateProductRelease creates a new release for a product. + rpc CreateProductRelease(CreateProductReleaseRequest) returns (ProductRelease) { + option (google.api.http) = { + post: "/v1/publisher/product-releases" + body: "*" + }; + } + + // UpdateProductRelease updates an existing product release. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateProductRelease(UpdateProductReleaseRequest) returns (ProductRelease) { + option (google.api.http) = { + put: "/v1/publisher/product-releases/{uuid}" + body: "*" + }; + } + + // DeleteProductRelease removes a product release. + rpc DeleteProductRelease(DeleteProductReleaseRequest) returns (DeleteProductReleaseResponse) { + option (google.api.http) = { + delete: "/v1/publisher/product-releases/{uuid}" + }; + } + + // ========================================================================== + // Component Management + // ========================================================================== + + // CreateComponent creates a new component. + rpc CreateComponent(CreateComponentRequest) returns (Component) { + option (google.api.http) = { + post: "/v1/publisher/components" + body: "*" + }; + } + + // UpdateComponent updates an existing component. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateComponent(UpdateComponentRequest) returns (Component) { + option (google.api.http) = { + put: "/v1/publisher/components/{uuid}" + body: "*" + }; + } + + // DeleteComponent removes a component and optionally its releases. + // Implementations SHOULD fail with FAILED_PRECONDITION when cascade=false and + // dependent releases still exist. + rpc DeleteComponent(DeleteComponentRequest) returns (DeleteComponentResponse) { + option (google.api.http) = { + delete: "/v1/publisher/components/{uuid}" + }; + } + + // CreateComponentRelease creates a new release for a component. + rpc CreateComponentRelease(CreateComponentReleaseRequest) returns (ComponentRelease) { + option (google.api.http) = { + post: "/v1/publisher/component-releases" + body: "*" + }; + } + + // UpdateComponentRelease updates an existing component release. + // Note: pre_release can only be changed from true to false, not vice versa. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateComponentRelease(UpdateComponentReleaseRequest) returns (ComponentRelease) { + option (google.api.http) = { + put: "/v1/publisher/component-releases/{uuid}" + body: "*" + }; + } + + // DeleteComponentRelease removes a component release. + rpc DeleteComponentRelease(DeleteComponentReleaseRequest) returns (DeleteComponentReleaseResponse) { + option (google.api.http) = { + delete: "/v1/publisher/component-releases/{uuid}" + }; + } + + // ========================================================================== + // Artifact Management + // ========================================================================== + + // UploadArtifact uploads a new artifact. + // Artifacts are immutable once created. The first streamed frame MUST contain + // metadata; subsequent frames carry content bytes. + rpc UploadArtifact(stream UploadArtifactRequest) returns (Artifact) { + option (google.api.http) = { + post: "/v1/publisher/artifacts" + body: "*" + }; + } + + // CreateArtifactFromUrl creates an artifact from an external URL. + // The server fetches the content, verifies the declared checksums, and then + // registers the artifact metadata. Implementations MAY reject non-HTTPS URLs + // or private-network source URLs for safety. + rpc CreateArtifactFromUrl(CreateArtifactFromUrlRequest) returns (Artifact) { + option (google.api.http) = { + post: "/v1/publisher/artifacts/from-url" + body: "*" + }; + } + + // DeleteArtifact removes an artifact. + // Artifacts in use by collections MUST NOT be deleted unless the + // implementation can safely and explicitly handle force deletion. + rpc DeleteArtifact(DeleteArtifactRequest) returns (DeleteArtifactResponse) { + option (google.api.http) = { + delete: "/v1/publisher/artifacts/{uuid}" + }; + } + + // ========================================================================== + // Collection Management + // ========================================================================== + + // CreateCollection creates version 1 of a logical collection stream. + // All referenced artifacts MUST already exist. + rpc CreateCollection(CreateCollectionRequest) returns (Collection) { + option (google.api.http) = { + post: "/v1/publisher/collections" + body: "*" + }; + } + + // UpdateCollection creates a new immutable version of an existing collection. + // Prior versions remain addressable. + rpc UpdateCollection(UpdateCollectionRequest) returns (Collection) { + option (google.api.http) = { + post: "/v1/publisher/collections/{uuid}/versions" + body: "*" + }; + } + + // SignCollection signs one collection version and returns the updated + // collection metadata. + rpc SignCollection(SignCollectionRequest) returns (Collection) { + option (google.api.http) = { + post: "/v1/publisher/collections/{uuid}/sign" + body: "*" + }; + } + + // ========================================================================== + // Batch Operations + // ========================================================================== + + // BatchUploadArtifacts uploads multiple artifacts in one session. + // This is an advanced optional publisher capability. + rpc BatchUploadArtifacts(stream BatchUploadArtifactsRequest) returns (BatchUploadArtifactsResponse) { + option (google.api.http) = { + post: "/v1/publisher/artifacts/batch" + body: "*" + }; + } + + // ImportCollection imports complete collection + artifact data. + // This is an advanced optional publisher capability used for bulk migration. + rpc ImportCollection(stream ImportCollectionRequest) returns (ImportCollectionResponse) { + option (google.api.http) = { + post: "/v1/publisher/import" + body: "*" + }; + } +} + +// ============================================================================ +// Product Request/Response Messages +// ============================================================================ + +// CreateProductRequest is the request to create a product. +message CreateProductRequest { + // Human-readable name. + string name = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 512, + (buf.validate.field).required = true + ]; + + // Optional description. + string description = 2; + + // Identifiers for the product. + repeated Identifier identifiers = 3; + + // Vendor information. + Vendor vendor = 4; + + // Optional homepage URL. + string homepage_url = 5 [json_name = "homepageUrl"]; + + // Optional documentation URL. + string documentation_url = 6 [json_name = "documentationUrl"]; + + // Optional VCS URL. + string vcs_url = 7 [json_name = "vcsUrl"]; + + // Client-provided UUID (optional, server generates if not provided). + optional string uuid = 8 [(buf.validate.field).string.uuid = true]; +} + +// UpdateProductRequest is the request to update a product. +message UpdateProductRequest { + // UUID of the product to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated name. + string name = 3; + + // Updated description. + string description = 4; + + // Updated identifiers (replaces existing). + repeated Identifier identifiers = 5; + + // Updated vendor information. + Vendor vendor = 6; + + // Updated homepage URL. + string homepage_url = 7 [json_name = "homepageUrl"]; + + // Updated documentation URL. + string documentation_url = 8 [json_name = "documentationUrl"]; + + // Updated VCS URL. + string vcs_url = 9 [json_name = "vcsUrl"]; +} + +// DeleteProductRequest is the request to delete a product. +message DeleteProductRequest { + // UUID of the product to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // If true, delete all releases as well. + bool cascade = 2; +} + +// DeleteProductResponse is the response from deleting a product. +message DeleteProductResponse { + // UUID of the deleted product. + string uuid = 1; + + // Number of releases deleted (if cascade=true). + int32 releases_deleted = 2 [json_name = "releasesDeleted"]; +} + +// CreateProductReleaseRequest is the request to create a product release. +message CreateProductReleaseRequest { + // UUID of the parent product (optional). + optional string product_uuid = 1 [(buf.validate.field).string.uuid = true, json_name = "productUuid"]; + + // Version string. + string version = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).required = true + ]; + + // Release date. + google.protobuf.Timestamp release_date = 3 [json_name = "releaseDate"]; + + // Pre-release flag. + bool pre_release = 4 [json_name = "preRelease"]; + + // Identifiers for the release. + repeated Identifier identifiers = 5; + + // Component references. + repeated ComponentRef components = 6; + + // Client-provided UUID (optional). + optional string uuid = 7 [(buf.validate.field).string.uuid = true]; +} + +// UpdateProductReleaseRequest is the request to update a product release. +message UpdateProductReleaseRequest { + // UUID of the product release to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated version. + string version = 3; + + // Updated release date. + google.protobuf.Timestamp release_date = 4 [json_name = "releaseDate"]; + + // Pre-release flag (can only change from true to false). + bool pre_release = 5 [json_name = "preRelease"]; + + // Updated identifiers. + repeated Identifier identifiers = 6; + + // Updated component references. + repeated ComponentRef components = 7; +} + +// DeleteProductReleaseRequest is the request to delete a product release. +message DeleteProductReleaseRequest { + // UUID of the product release to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// DeleteProductReleaseResponse is the response from deleting a product release. +message DeleteProductReleaseResponse { + // UUID of the deleted product release. + string uuid = 1; +} + +// ============================================================================ +// Component Request/Response Messages +// ============================================================================ + +// CreateComponentRequest is the request to create a component. +message CreateComponentRequest { + // Human-readable name. + string name = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).required = true + ]; + + // Optional description. + string description = 2; + + // Identifiers for the component. + repeated Identifier identifiers = 3; + + // Component type. + ComponentType component_type = 4 [json_name = "componentType"]; + + // License information. + repeated LicenseInfo licenses = 5; + + // Publisher name. + string publisher = 6; + + // Homepage URL. + string homepage_url = 7 [json_name = "homepageUrl"]; + + // VCS URL. + string vcs_url = 8 [json_name = "vcsUrl"]; + + // Client-provided UUID (optional). + optional string uuid = 9 [(buf.validate.field).string.uuid = true]; +} + +// UpdateComponentRequest is the request to update a component. +message UpdateComponentRequest { + // UUID of the component to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated name. + string name = 3; + + // Updated description. + string description = 4; + + // Updated identifiers. + repeated Identifier identifiers = 5; + + // Updated component type. + ComponentType component_type = 6 [json_name = "componentType"]; + + // Updated licenses. + repeated LicenseInfo licenses = 7; + + // Updated publisher. + string publisher = 8; + + // Updated homepage URL. + string homepage_url = 9 [json_name = "homepageUrl"]; + + // Updated VCS URL. + string vcs_url = 10 [json_name = "vcsUrl"]; +} + +// DeleteComponentRequest is the request to delete a component. +message DeleteComponentRequest { + // UUID of the component to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // If true, delete all releases as well. + bool cascade = 2; +} + +// DeleteComponentResponse is the response from deleting a component. +message DeleteComponentResponse { + // UUID of the deleted component. + string uuid = 1; + + // Number of releases deleted (if cascade=true). + int32 releases_deleted = 2 [json_name = "releasesDeleted"]; +} + +// CreateComponentReleaseRequest is the request to create a component release. +message CreateComponentReleaseRequest { + // UUID of the parent component. + string component_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "componentUuid" + ]; + + // Version string. + string version = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).required = true + ]; + + // Release date. + google.protobuf.Timestamp release_date = 3 [json_name = "releaseDate"]; + + // Pre-release flag. + bool pre_release = 4 [json_name = "preRelease"]; + + // Identifiers for the release. + repeated Identifier identifiers = 5; + + // Distributions. + repeated Distribution distributions = 6; + + // Client-provided UUID (optional). + optional string uuid = 7 [(buf.validate.field).string.uuid = true]; +} + +// UpdateComponentReleaseRequest is the request to update a component release. +message UpdateComponentReleaseRequest { + // UUID of the component release to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated version. + string version = 3; + + // Updated release date. + google.protobuf.Timestamp release_date = 4 [json_name = "releaseDate"]; + + // Pre-release flag (can only change from true to false). + bool pre_release = 5 [json_name = "preRelease"]; + + // Updated identifiers. + repeated Identifier identifiers = 6; + + // Updated distributions. + repeated Distribution distributions = 7; +} + +// DeleteComponentReleaseRequest is the request to delete a component release. +message DeleteComponentReleaseRequest { + // UUID of the component release to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// DeleteComponentReleaseResponse is the response from deleting a component release. +message DeleteComponentReleaseResponse { + // UUID of the deleted component release. + string uuid = 1; +} + +// ============================================================================ +// Artifact Request/Response Messages +// ============================================================================ + +// UploadArtifactRequest is used for streaming artifact uploads. +message UploadArtifactRequest { + // Streaming payload frames for an artifact upload. + oneof data { + // Metadata (must be sent first). + ArtifactMetadata metadata = 1; + + // Content chunk. + bytes content = 2; + } +} + +// ArtifactMetadata contains metadata for artifact upload or URL registration. +message ArtifactMetadata { + // Human-readable name. + string name = 1 [(buf.validate.field).required = true]; + + // Artifact type. + ArtifactType type = 2 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}]; + + // MIME type of the content. + string mime_type = 3 [json_name = "mimeType", (buf.validate.field).required = true]; + + // Optional description. + string description = 4; + + // Distribution types this applies to. + repeated string component_distributions = 5 [json_name = "componentDistributions"]; + + // Subject of the artifact. + ArtifactSubject subject = 6; + + // Spec version (e.g., "1.5" for CycloneDX). + string spec_version = 7 [json_name = "specVersion"]; + + // Client-provided UUID (optional). + optional string uuid = 8 [(buf.validate.field).string.uuid = true]; + + // Expected checksums for verification. + // Implementations SHOULD verify uploaded or fetched content against these + // values before persisting the artifact record. + repeated Checksum expected_checksums = 9 [json_name = "expectedChecksums"]; +} + +// CreateArtifactFromUrlRequest creates an artifact from an external URL. +message CreateArtifactFromUrlRequest { + // Artifact metadata. + ArtifactMetadata metadata = 1 [(buf.validate.field).required = true]; + + // URL to fetch the content from. + // This SHOULD point to immutable content. Implementations MAY enforce HTTPS + // and MAY reject private-network targets. + string source_url = 2 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).required = true, + json_name = "sourceUrl" + ]; + + // Expected checksums for verification. + // If both metadata.expected_checksums and this field are populated, they MUST + // describe the same checksum set or the request should be rejected. + repeated Checksum expected_checksums = 3 [json_name = "expectedChecksums"]; + + // Optional signature URL. + string signature_url = 4 [json_name = "signatureUrl"]; +} + +// DeleteArtifactRequest is the request to delete an artifact. +message DeleteArtifactRequest { + // UUID of the artifact to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Force deletion even if artifact is in use. + // Implementations MAY reject this flag when referential cleanup is not + // supported safely. + bool force = 2; +} + +// DeleteArtifactResponse is the response from deleting an artifact. +message DeleteArtifactResponse { + // UUID of the deleted artifact. + string uuid = 1; + + // Collections that referenced this artifact (if forced). + repeated string affected_collection_uuids = 2 [json_name = "affectedCollectionUuids"]; +} + +// ============================================================================ +// Collection Request/Response Messages +// ============================================================================ + +// CreateCollectionRequest is the request to create a collection. +message CreateCollectionRequest { + // Logical collection UUID. + // For release-scoped collections this is typically the referenced release + // UUID and remains stable across collection versions. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Scope of the collection. + CollectionScope belongs_to = 2 [json_name = "belongsTo", (buf.validate.field).required = true]; + + // UUIDs of artifacts to include in version 1. + repeated string artifact_uuids = 3 [json_name = "artifactUuids"]; + + // Update reason (typically INITIAL_RELEASE for version 1). + UpdateReason update_reason = 4 [json_name = "updateReason"]; +} + +// UpdateCollectionRequest creates a new version of a collection. +message UpdateCollectionRequest { + // UUID of the collection to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Full replacement set of artifacts in the new version. + repeated string artifact_uuids = 2 [json_name = "artifactUuids"]; + + // Reason for the update. + UpdateReason update_reason = 3 [json_name = "updateReason", (buf.validate.field).required = true]; +} + +// SignCollectionRequest signs a collection version. +message SignCollectionRequest { + // UUID of the collection to sign. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Version to sign (latest if omitted). + optional int32 version = 2; + + // Key ID to use for signing. + string key_id = 3 [json_name = "keyId"]; + + // Use Sigstore for signing. + bool use_sigstore = 4 [json_name = "useSigstore"]; +} + +// ============================================================================ +// Batch Operations +// ============================================================================ + +// BatchUploadArtifactsRequest is used for batch artifact uploads. +message BatchUploadArtifactsRequest { + // Streaming payload frames for a batch upload. + oneof data { + // Batch metadata (must be sent first). + BatchArtifactMetadata batch_metadata = 1; + + // Single artifact data. + UploadArtifactRequest artifact = 2; + } +} + +// BatchArtifactMetadata contains metadata for batch upload. +message BatchArtifactMetadata { + // Total number of artifacts in this batch. + int32 total_count = 1 [json_name = "totalCount"]; + + // Target collection UUID (optional). + optional string collection_uuid = 2 [json_name = "collectionUuid"]; +} + +// BatchUploadArtifactsResponse contains results of batch upload. +message BatchUploadArtifactsResponse { + // Successfully uploaded artifacts. + repeated Artifact artifacts = 1; + + // Failed uploads. + repeated BatchUploadError errors = 2; +} + +// BatchUploadError describes a failed upload in a batch. +message BatchUploadError { + // Index in the batch. + int32 index = 1; + + // Artifact name (if available). + string name = 2; + + // Error details. + ErrorDetail error = 3; +} + +// ImportCollectionRequest is used for bulk import. +message ImportCollectionRequest { + // Streaming payload frames for a collection import. + oneof data { + // Import metadata (must be sent first). + ImportMetadata import_metadata = 1; + + // Collection data. + Collection collection = 2; + + // Artifact with content. + ArtifactWithContent artifact = 3; + } +} + +// ImportMetadata contains metadata for bulk import. +message ImportMetadata { + // Source system identifier. + string source_system = 1 [json_name = "sourceSystem"]; + + // Total collections in import. + int32 total_collections = 2 [json_name = "totalCollections"]; + + // Total artifacts in import. + int32 total_artifacts = 3 [json_name = "totalArtifacts"]; + + // Overwrite existing data. + bool overwrite = 4; +} + +// ArtifactWithContent includes artifact metadata and content. +message ArtifactWithContent { + // Artifact metadata. + Artifact artifact = 1; + + // Content for each format. + repeated ArtifactFormatContent format_contents = 2 [json_name = "formatContents"]; +} + +// ArtifactFormatContent contains content for one format. +message ArtifactFormatContent { + // MIME type. + string mime_type = 1 [json_name = "mimeType"]; + + // Content bytes. + bytes content = 2; +} + +// ImportCollectionResponse contains results of bulk import. +message ImportCollectionResponse { + // Number of collections imported. + int32 collections_imported = 1 [json_name = "collectionsImported"]; + + // Number of artifacts imported. + int32 artifacts_imported = 2 [json_name = "artifactsImported"]; + + // Errors during import. + repeated ImportError errors = 3; +} + +// ImportError describes an error during import. +message ImportError { + // Type of entity that failed. + string entity_type = 1 [json_name = "entityType"]; + + // UUID of the entity. + string uuid = 2; + + // Error details. + ErrorDetail error = 3; +}