diff --git a/proto/tea/v1/artifact.proto b/proto/tea/v1/artifact.proto new file mode 100644 index 0000000..5028174 --- /dev/null +++ b/proto/tea/v1/artifact.proto @@ -0,0 +1,364 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; +import "buf/validate/validate.proto"; + +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.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"; + +// ============================================================================ +// TEA Artifact +// ============================================================================ + +// Artifact represents a TEA Artifact - a security-related document or file +// linked to a component release, such as an SBOM, VEX, attestation, or license. +// +// TEA Artifacts are strictly IMMUTABLE: if the underlying document changes, +// a new TEA Artifact object must be created. URLs referenced in this object +// must always resolve to the same resource to ensure published checksums +// remain valid and verifiable. +// +// TEA Artifacts can be reused across multiple TEA Collections, allowing the +// same document to be referenced by different component or product releases. +message Artifact { + // Unique identifier for this TEA Artifact. + // Format: UUID v4 or v7. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // Human-readable name for the artifact. + string name = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 512 + ]; + + // Type of the artifact (SBOM, VEX, attestation, etc.). + ArtifactType type = 3 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}]; + + // Distribution types this artifact applies to. + // If empty, the artifact applies to ALL distributions of the release. + // Values must match distributionType values from ComponentRelease.distributions. + repeated string component_distributions = 4 [json_name = "componentDistributions"]; + + // Available formats for this artifact. + // The same artifact content may be available in multiple formats + // (e.g., JSON and XML for CycloneDX). + repeated ArtifactFormat formats = 5 [(buf.validate.field).repeated.min_items = 1]; + + // Timestamp when this artifact was created in the TEA system. + google.protobuf.Timestamp created_date = 6 [json_name = "createdDate"]; + + // Optional description of the artifact. + string description = 7 [(buf.validate.field).string.max_len = 4096]; + + // Subject of the artifact (what it describes). + // For BOMs, this typically references the component/product. + ArtifactSubject subject = 8; + + // Optional deprecation information. + optional Deprecation deprecation = 9; +} + +// ============================================================================ +// Artifact Types +// ============================================================================ + +// ArtifactType classifies the type of transparency artifact. +enum ArtifactType { + // Unspecified artifact type - should not be used. + ARTIFACT_TYPE_UNSPECIFIED = 0; + + // Machine-readable statements containing facts, evidence, or testimony. + // Examples: in-toto attestations, SLSA provenance. + ARTIFACT_TYPE_ATTESTATION = 1; + + // Bill of Materials: SBOM, OBOM, HBOM, SaaSBOM, AI/ML-BOM, etc. + // Format-agnostic (CycloneDX, SPDX, etc.). + ARTIFACT_TYPE_BOM = 2; + + // Build-system specific metadata file. + // Examples: pom.xml, package.json, .nuspec, Cargo.toml. + ARTIFACT_TYPE_BUILD_META = 3; + + // Industry, regulatory, or other certification from an accredited + // certification body. + ARTIFACT_TYPE_CERTIFICATION = 4; + + // Describes how a component or service was manufactured or deployed. + // Includes build formulas, deployment manifests, IaC. + ARTIFACT_TYPE_FORMULATION = 5; + + // License file or license information. + ARTIFACT_TYPE_LICENSE = 6; + + // Release notes document. + ARTIFACT_TYPE_RELEASE_NOTES = 7; + + // A security.txt file (RFC 9116). + ARTIFACT_TYPE_SECURITY_TXT = 8; + + // A threat model document. + // Includes DFDs, attack trees, STRIDE analysis. + ARTIFACT_TYPE_THREAT_MODEL = 9; + + // Vulnerability information: VDR (Vulnerability Disclosure Report) + // or VEX (Vulnerability Exploitability eXchange). + ARTIFACT_TYPE_VULNERABILITIES = 10; + + // Common Lifecycle Enumeration document. + ARTIFACT_TYPE_CLE = 11; + + // CDXA - CycloneDX Attestations. + ARTIFACT_TYPE_CDXA = 12; + + // Cryptographic Bill of Materials (CBOM). + ARTIFACT_TYPE_CBOM = 13; + + // Model card for ML models. + ARTIFACT_TYPE_MODEL_CARD = 14; + + // Static analysis report (SARIF or proprietary). + ARTIFACT_TYPE_STATIC_ANALYSIS = 15; + + // Dynamic analysis report. + ARTIFACT_TYPE_DYNAMIC_ANALYSIS = 16; + + // Penetration test report. + ARTIFACT_TYPE_PENTEST_REPORT = 17; + + // Risk assessment document. + ARTIFACT_TYPE_RISK_ASSESSMENT = 18; + + // Plans of Action and Milestones (POAM). + ARTIFACT_TYPE_POAM = 19; + + // Quality metrics report. + ARTIFACT_TYPE_QUALITY_METRICS = 20; + + // Test harness or integration test suite. + ARTIFACT_TYPE_HARNESS = 21; + + // Conformance test report or compliance verification. + ARTIFACT_TYPE_CONFORMANCE = 22; + + // Other document type not covered above. + ARTIFACT_TYPE_OTHER = 99; +} + +// ============================================================================ +// Artifact Format +// ============================================================================ + +// ArtifactFormat represents a specific encoding/format of an artifact. +// The same artifact content may be available in multiple formats. +message ArtifactFormat { + // MIME type of the document. + // Examples: + // - application/vnd.cyclonedx+json + // - application/vnd.cyclonedx+xml + // - application/spdx+json + // - application/json + // - application/xml + // - text/plain + string mime_type = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 256, + json_name = "mimeType" + ]; + + // Human-readable description of this format. + // Example: "CycloneDX SBOM (XML format)" + string description = 2 [(buf.validate.field).string.max_len = 1024]; + + // Direct download URL for the artifact in this format. + // Must point to an immutable resource. + string url = 3 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).required = true + ]; + + // Optional URL for a detached digital signature of the artifact. + // Common formats: .asc (PGP), .sig (GPG), .p7s (PKCS#7). + string signature_url = 4 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "signatureUrl" + ]; + + // Checksums for integrity verification. + // At least SHA-256 is recommended. + repeated Checksum checksums = 5 [(buf.validate.field).repeated.min_items = 1]; + + // File size in bytes. + optional int64 size_bytes = 6 [json_name = "sizeBytes"]; + + // Encoding of the content (e.g., "utf-8", "base64"). + string encoding = 7; + + // Specification version (for typed artifacts). + // Example: "1.5" for CycloneDX 1.5, "2.3" for SPDX 2.3. + string spec_version = 8 [json_name = "specVersion"]; +} + +// ============================================================================ +// Artifact Subject +// ============================================================================ + +// ArtifactSubject describes what entity an artifact is about. +message ArtifactSubject { + // Type of subject. + SubjectType type = 1; + + // Identifiers for the subject. + repeated Identifier identifiers = 2; + + // Human-readable name of the subject. + string name = 3; + + // Version of the subject (if applicable). + string version = 4; +} + +// SubjectType classifies what an artifact describes. +enum SubjectType { + // Unspecified subject type. + SUBJECT_TYPE_UNSPECIFIED = 0; + + // The artifact describes a component. + SUBJECT_TYPE_COMPONENT = 1; + + // The artifact describes a product. + SUBJECT_TYPE_PRODUCT = 2; + + // The artifact describes a service. + SUBJECT_TYPE_SERVICE = 3; + + // The artifact describes an organization. + SUBJECT_TYPE_ORGANIZATION = 4; + + // The artifact describes a build/release process. + SUBJECT_TYPE_BUILD = 5; +} + +// ============================================================================ +// Artifact Content (for streaming downloads) +// ============================================================================ + +// ArtifactContentChunk is used for streaming artifact content. +message ArtifactContentChunk { + // Chunk of content data. + bytes data = 1; + + // Offset of this chunk in the full content. + int64 offset = 2; + + // Total size of the full content (in first chunk). + optional int64 total_size = 3 [json_name = "totalSize"]; + + // MIME type of the content (in first chunk). + string mime_type = 4 [json_name = "mimeType"]; + + // ETag for caching/conditional requests. + string etag = 5; +} + +// ============================================================================ +// Request/Response Messages +// ============================================================================ + +// GetArtifactRequest is the request for getting artifact metadata. +message GetArtifactRequest { + // UUID of the artifact to retrieve. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// GetArtifactContentRequest is the request for downloading artifact content. +message GetArtifactContentRequest { + // UUID of the artifact. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Preferred format (MIME type). If not available, server chooses. + string preferred_format = 2 [json_name = "preferredFormat"]; + + // If-None-Match header for conditional requests. + // If the ETag matches, server returns NOT_MODIFIED. + string if_none_match = 3 [json_name = "ifNoneMatch"]; + + // Range request (for partial downloads). + // Format: "bytes=start-end" + string range = 4; +} + +// GetArtifactContentResponse is the response containing artifact content. +// For large artifacts, use streaming RPC instead. +message GetArtifactContentResponse { + // Artifact content. + bytes content = 1; + + // MIME type of the content. + string mime_type = 2 [json_name = "mimeType"]; + + // ETag for caching. + string etag = 3; + + // Content length. + int64 content_length = 4 [json_name = "contentLength"]; + + // Last modified timestamp. + google.protobuf.Timestamp last_modified = 5 [json_name = "lastModified"]; +} + +// ListArtifactsRequest lists artifacts with optional filters. +message ListArtifactsRequest { + // Pagination parameters. + PageRequest pagination = 1; + + // Filter by artifact type. + ArtifactType type = 2; + + // Filter by MIME type. + string mime_type = 3 [json_name = "mimeType"]; + + // Optional sort specification. + SortSpec sort = 4; +} + +// ListArtifactsResponse is the response for listing artifacts. +message ListArtifactsResponse { + // List of artifacts. + repeated Artifact artifacts = 1; + + // Pagination information. + PageResponse pagination = 2; +} + +// SearchArtifactsBySubjectRequest finds artifacts by their subject. +message SearchArtifactsBySubjectRequest { + // Subject identifier to search for. + Identifier identifier = 1 [(buf.validate.field).required = true]; + + // Filter by artifact type. + ArtifactType type = 2; + + // Pagination parameters. + PageRequest pagination = 3; +} + +// SearchArtifactsBySubjectResponse contains matching artifacts. +message SearchArtifactsBySubjectResponse { + // Matching artifacts. + repeated Artifact artifacts = 1; + + // Pagination information. + PageResponse pagination = 2; +} \ No newline at end of file diff --git a/proto/tea/v1/collection.proto b/proto/tea/v1/collection.proto new file mode 100644 index 0000000..0facc2a --- /dev/null +++ b/proto/tea/v1/collection.proto @@ -0,0 +1,316 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.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"; + +// ============================================================================ +// TEA Collection +// ============================================================================ + +// Collection represents a TEA Collection - a versioned list of artifacts for a +// specific ComponentRelease or ProductRelease. +// +// Collections are versioned to indicate changes (e.g., an updated VEX or +// corrected SBOM). When artifacts are updated, a new Collection version is +// created with the same UUID but incremented version number. +// +// The UUID of a Collection for a ComponentRelease is identical to the +// ComponentRelease UUID. For ProductReleases, it's the ProductRelease UUID. +message Collection { + // Unique identifier for this TEA Collection. + // For ComponentRelease collections: equals the ComponentRelease UUID. + // For ProductRelease collections: equals the ProductRelease UUID. + // Format: UUID v4 or v7. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // Collection version number. + // Incremented each time the collection content changes. + // Starts at 1 for new collections. + int32 version = 2 [ + (buf.validate.field).int32.gte = 1, + (buf.validate.field).required = true + ]; + + // Timestamp when this collection version was created. + google.protobuf.Timestamp date = 3; + + // Scope of this collection - indicates what the collection belongs to. + CollectionScope belongs_to = 4 [json_name = "belongsTo"]; + + // Reason for this collection version update. + UpdateReason update_reason = 5 [json_name = "updateReason"]; + + // List of TEA Artifacts in this collection. + repeated Artifact artifacts = 6; + + // Digital signature of the collection (if signed). + optional CollectionSignature signature = 7; + + // Timestamp when this collection was created in the TEA system. + google.protobuf.Timestamp created_date = 8 [json_name = "createdDate"]; + + // Optional deprecation information. + optional Deprecation deprecation = 9; + + // List of conformance vectors (compliance standards or frameworks) this collection complies with. + // Examples: "NIST SP 800-53", "OWASP Top 10", "ISO 27001", "PCI DSS", "GDPR". + // These indicate the security, privacy, or regulatory standards met by the artifacts in this collection. + repeated string conformance_vectors = 10 [json_name = "conformanceVectors"]; +} + +// ============================================================================ +// Collection Scope +// ============================================================================ + +// CollectionScope indicates what entity the collection belongs to. +enum CollectionScope { + // Unspecified scope. + COLLECTION_SCOPE_UNSPECIFIED = 0; + + // Collection belongs to a ComponentRelease. + COLLECTION_SCOPE_RELEASE = 1; + + // Collection belongs to a ProductRelease. + COLLECTION_SCOPE_PRODUCT_RELEASE = 2; +} + +// ============================================================================ +// Update Reason +// ============================================================================ + +// UpdateReasonType categorizes why a collection was updated. +enum UpdateReasonType { + // Unspecified update reason. + UPDATE_REASON_TYPE_UNSPECIFIED = 0; + + // Initial release of the collection. + UPDATE_REASON_TYPE_INITIAL_RELEASE = 1; + + // VEX (Vulnerability Exploitability eXchange) artifact was updated. + // Updates to VEX may produce different alerts in TEA clients. + UPDATE_REASON_TYPE_VEX_UPDATED = 2; + + // One or more artifacts (other than VEX) were updated. + UPDATE_REASON_TYPE_ARTIFACT_UPDATED = 3; + + // One or more artifacts were removed from the collection. + UPDATE_REASON_TYPE_ARTIFACT_REMOVED = 4; + + // One or more artifacts were added to the collection. + UPDATE_REASON_TYPE_ARTIFACT_ADDED = 5; + + // Correction to metadata (not artifact content). + UPDATE_REASON_TYPE_METADATA_CORRECTION = 6; + + // Security-related update requiring immediate attention. + UPDATE_REASON_TYPE_SECURITY_UPDATE = 7; +} + +// UpdateReason provides context for why a collection version was created. +message UpdateReason { + // Type of update. + UpdateReasonType type = 1 [(buf.validate.field).enum = {defined_only: true}]; + + // Human-readable description of the update. + string comment = 2 [(buf.validate.field).string.max_len = 4096]; + + // UUIDs of affected artifacts (for update/remove/add reasons). + repeated string affected_artifact_uuids = 3 [json_name = "affectedArtifactUuids"]; +} + +// ============================================================================ +// Collection Signature +// ============================================================================ + +// SignatureAlgorithm specifies the algorithm used for digital signatures. +enum SignatureAlgorithm { + // Unspecified algorithm. + SIGNATURE_ALGORITHM_UNSPECIFIED = 0; + + // RSA with SHA-256 (RSASSA-PKCS1-v1_5). + SIGNATURE_ALGORITHM_RS256 = 1; + + // RSA with SHA-384. + SIGNATURE_ALGORITHM_RS384 = 2; + + // RSA with SHA-512. + SIGNATURE_ALGORITHM_RS512 = 3; + + // ECDSA with P-256 and SHA-256. + SIGNATURE_ALGORITHM_ES256 = 4; + + // ECDSA with P-384 and SHA-384. + SIGNATURE_ALGORITHM_ES384 = 5; + + // ECDSA with P-521 and SHA-512. + SIGNATURE_ALGORITHM_ES512 = 6; + + // EdDSA with Ed25519. + SIGNATURE_ALGORITHM_EDDSA = 7; + + // RSA-PSS with SHA-256. + SIGNATURE_ALGORITHM_PS256 = 8; + + // RSA-PSS with SHA-384. + SIGNATURE_ALGORITHM_PS384 = 9; + + // RSA-PSS with SHA-512. + SIGNATURE_ALGORITHM_PS512 = 10; +} + +// CollectionSignature contains the digital signature of a collection. +message CollectionSignature { + // Signature algorithm used. + SignatureAlgorithm algorithm = 1; + + // Base64-encoded signature value. + string value = 2 [(buf.validate.field).string.min_len = 1]; + + // Timestamp when the signature was created. + google.protobuf.Timestamp signed_at = 3 [json_name = "signedAt"]; + + // Key identifier (for key lookup/verification). + string key_id = 4 [json_name = "keyId"]; + + // Optional X.509 certificate chain (PEM-encoded). + repeated string certificate_chain = 5 [json_name = "certificateChain"]; + + // Sigstore bundle (if Sigstore/cosign was used). + optional SigstoreBundle sigstore_bundle = 6 [json_name = "sigstoreBundle"]; +} + +// SigstoreBundle contains Sigstore-specific signature information. +message SigstoreBundle { + // Rekor log entry URL. + string rekor_log_url = 1 [json_name = "rekorLogUrl"]; + + // Rekor log entry ID. + string rekor_log_id = 2 [json_name = "rekorLogId"]; + + // Fulcio certificate (PEM-encoded). + string fulcio_certificate = 3 [json_name = "fulcioCertificate"]; + + // Timestamp authority response (RFC 3161). + bytes timestamp_authority_response = 4 [json_name = "timestampAuthorityResponse"]; +} + +// ============================================================================ +// Collection Version Info +// ============================================================================ + +// CollectionVersionInfo provides summary information about a collection version +// without including the full artifact list. +message CollectionVersionInfo { + // Collection UUID. + string uuid = 1; + + // Version number. + int32 version = 2; + + // Creation timestamp. + google.protobuf.Timestamp date = 3; + + // Update reason summary. + UpdateReason update_reason = 4 [json_name = "updateReason"]; + + // Number of artifacts in this version. + int32 artifact_count = 5 [json_name = "artifactCount"]; + + // Whether this version is signed. + bool is_signed = 6 [json_name = "isSigned"]; +} + +// ============================================================================ +// Request/Response Messages +// ============================================================================ + +// GetCollectionRequest is the request for getting the latest collection version. +message GetCollectionRequest { + // UUID of the collection to retrieve. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Include artifact content metadata in the response. + // If false, only artifact UUIDs are returned. + bool include_artifacts = 2 [json_name = "includeArtifacts"]; +} + +// ListCollectionVersionsRequest is the request for listing all versions of a collection. +message ListCollectionVersionsRequest { + // UUID of the collection. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Pagination parameters. + PageRequest pagination = 2; + + // Include full artifact details (default: false, summary only). + bool include_artifacts = 3 [json_name = "includeArtifacts"]; +} + +// ListCollectionVersionsResponse is the response for listing collection versions. +message ListCollectionVersionsResponse { + // Collection version summaries (or full collections if include_artifacts=true). + repeated CollectionVersionInfo versions = 1; + + // Full collection data (only populated if include_artifacts=true). + repeated Collection collections = 2; + + // Pagination information. + PageResponse pagination = 3; +} + +// GetCollectionVersionRequest is the request for getting a specific collection version. +message GetCollectionVersionRequest { + // UUID of the collection. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Version number to retrieve. + int32 version = 2 [(buf.validate.field).int32.gte = 1]; +} + +// CompareCollectionVersionsRequest compares two versions of a collection. +message CompareCollectionVersionsRequest { + // UUID of the collection. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // First version to compare (base). + int32 base_version = 2 [(buf.validate.field).int32.gte = 1, json_name = "baseVersion"]; + + // Second version to compare (target). + int32 target_version = 3 [(buf.validate.field).int32.gte = 1, json_name = "targetVersion"]; +} + +// CompareCollectionVersionsResponse shows differences between two collection versions. +message CompareCollectionVersionsResponse { + // UUID of the collection. + string uuid = 1; + + // Base version number. + int32 base_version = 2 [json_name = "baseVersion"]; + + // Target version number. + int32 target_version = 3 [json_name = "targetVersion"]; + + // Artifacts added in target version. + repeated string added_artifact_uuids = 4 [json_name = "addedArtifactUuids"]; + + // Artifacts removed in target version. + repeated string removed_artifact_uuids = 5 [json_name = "removedArtifactUuids"]; + + // Artifacts modified in target version (same UUID, different content). + repeated string modified_artifact_uuids = 6 [json_name = "modifiedArtifactUuids"]; +} \ No newline at end of file diff --git a/proto/tea/v1/common.proto b/proto/tea/v1/common.proto new file mode 100644 index 0000000..57e9cbe --- /dev/null +++ b/proto/tea/v1/common.proto @@ -0,0 +1,371 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/protobuf/timestamp.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"; + +// ============================================================================ +// Identifier Types +// ============================================================================ + +// IdentifierType defines the type of identifier used to identify a product, +// component, or release in the transparency ecosystem. +enum IdentifierType { + // Unspecified identifier type - should not be used in valid data. + IDENTIFIER_TYPE_UNSPECIFIED = 0; + + // TEI - Transparency Exchange Identifier + // Format: urn:tei::: + // Example: urn:tei:uuid:cyclonedx.org:d4d9f54a-abcf-11ee-ac79-1a52914d44b1 + IDENTIFIER_TYPE_TEI = 1; + + // PURL - Package URL (https://github.com/package-url/purl-spec) + // Example: pkg:maven/org.apache.logging.log4j/log4j-core@2.24.3 + IDENTIFIER_TYPE_PURL = 2; + + // CPE - Common Platform Enumeration (https://nvd.nist.gov/products/cpe) + // Example: cpe:2.3:a:apache:log4j:2.24.3:*:*:*:*:*:*:* + IDENTIFIER_TYPE_CPE = 3; + + // SWID - Software Identification Tag (ISO/IEC 19770-2) + IDENTIFIER_TYPE_SWID = 4; + + // GAV - Maven Group:Artifact:Version coordinates + // Example: org.apache.logging.log4j:log4j-core:2.24.3 + IDENTIFIER_TYPE_GAV = 5; + + // GTIN - Global Trade Item Number (EAN/UPC barcodes) + // Example: 1234567890123 + IDENTIFIER_TYPE_GTIN = 6; + + // GMN - Global Model Number + IDENTIFIER_TYPE_GMN = 7; + + // UDI - Unique Device Identification (medical devices) + IDENTIFIER_TYPE_UDI = 8; + + // ASIN - Amazon Standard Identification Number + IDENTIFIER_TYPE_ASIN = 9; + + // HASH - Content hash identifier + // Format varies by hash algorithm + IDENTIFIER_TYPE_HASH = 10; +} + +// Identifier represents a typed identifier for a TEA entity. +// Identifiers are immutable and globally unique within their type namespace. +message Identifier { + // Type of the identifier. + IdentifierType id_type = 1 [ + (buf.validate.field).enum = {defined_only: true, not_in: [0]}, + json_name = "idType" + ]; + + // Value of the identifier in its canonical string form. + // Must conform to the format specification of the identifier type. + string id_value = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 2048, + json_name = "idValue" + ]; +} + +// ============================================================================ +// Checksum Types +// ============================================================================ + +// ChecksumAlgorithm defines the cryptographic hash algorithm used for checksums. +// Algorithms are ordered by security strength (weakest to strongest). +enum ChecksumAlgorithm { + // Unspecified algorithm - should not be used in valid data. + CHECKSUM_ALGORITHM_UNSPECIFIED = 0; + + // MD5 - 128-bit hash (DEPRECATED: cryptographically broken) + // Only for legacy compatibility, not recommended for new data. + CHECKSUM_ALGORITHM_MD5 = 1 [deprecated = true]; + + // SHA-1 - 160-bit hash (DEPRECATED: cryptographically weak) + // Only for legacy compatibility, not recommended for new data. + CHECKSUM_ALGORITHM_SHA1 = 2 [deprecated = true]; + + // SHA-256 - 256-bit hash (RECOMMENDED minimum) + CHECKSUM_ALGORITHM_SHA256 = 3; + + // SHA-384 - 384-bit hash + CHECKSUM_ALGORITHM_SHA384 = 4; + + // SHA-512 - 512-bit hash + CHECKSUM_ALGORITHM_SHA512 = 5; + + // SHA3-256 - 256-bit Keccak-based hash + CHECKSUM_ALGORITHM_SHA3_256 = 6; + + // SHA3-384 - 384-bit Keccak-based hash + CHECKSUM_ALGORITHM_SHA3_384 = 7; + + // SHA3-512 - 512-bit Keccak-based hash + CHECKSUM_ALGORITHM_SHA3_512 = 8; + + // BLAKE2b-256 - 256-bit BLAKE2b hash + CHECKSUM_ALGORITHM_BLAKE2B_256 = 9; + + // BLAKE2b-384 - 384-bit BLAKE2b hash + CHECKSUM_ALGORITHM_BLAKE2B_384 = 10; + + // BLAKE2b-512 - 512-bit BLAKE2b hash + CHECKSUM_ALGORITHM_BLAKE2B_512 = 11; + + // BLAKE3 - Variable-length BLAKE3 hash (default 256-bit) + CHECKSUM_ALGORITHM_BLAKE3 = 12; +} + +// Checksum represents a cryptographic hash of content for integrity verification. +message Checksum { + // Algorithm used to compute the checksum. + ChecksumAlgorithm alg_type = 1 [ + (buf.validate.field).enum = {defined_only: true, not_in: [0]}, + json_name = "algType" + ]; + + // Hexadecimal-encoded checksum value (lowercase). + string alg_value = 2 [ + (buf.validate.field).string.pattern = "^[a-f0-9]+$", + (buf.validate.field).string.min_len = 32, + (buf.validate.field).string.max_len = 256, + json_name = "algValue" + ]; +} + +// ============================================================================ +// Pagination +// ============================================================================ + +// PageRequest specifies pagination parameters for list operations. +message PageRequest { + // Maximum number of items to return per page. + // Server may return fewer items. Default is 20, maximum is 100. + int32 page_size = 1 [ + (buf.validate.field).int32 = {gte: 1, lte: 100}, + json_name = "pageSize" + ]; + + // Opaque token for fetching the next page of results. + // Obtained from PageResponse.next_page_token of a previous request. + // Omit for the first page. + string page_token = 2 [json_name = "pageToken"]; +} + +// PageResponse contains pagination metadata in list responses. +message PageResponse { + // Token to retrieve the next page of results. + // Empty if there are no more results. + string next_page_token = 1 [json_name = "nextPageToken"]; + + // Total number of items across all pages (if known). + // May be omitted if the total is expensive to compute. + optional int64 total_count = 2 [json_name = "totalCount"]; +} + +// ============================================================================ +// Error Types +// ============================================================================ + +// ErrorCode defines standard error codes for TEA API responses. +// These map to HTTP status codes where applicable. +enum ErrorCode { + // Unspecified error - should not be used. + ERROR_CODE_UNSPECIFIED = 0; + + // The request was invalid or malformed (HTTP 400). + ERROR_CODE_INVALID_ARGUMENT = 1; + + // Authentication credentials were missing or invalid (HTTP 401). + ERROR_CODE_UNAUTHENTICATED = 2; + + // The caller does not have permission for this operation (HTTP 403). + ERROR_CODE_PERMISSION_DENIED = 3; + + // The requested resource was not found (HTTP 404). + ERROR_CODE_NOT_FOUND = 4; + + // The resource already exists (HTTP 409). + ERROR_CODE_ALREADY_EXISTS = 5; + + // The operation was rejected due to resource exhaustion (HTTP 429). + ERROR_CODE_RESOURCE_EXHAUSTED = 6; + + // The operation was cancelled (HTTP 499). + ERROR_CODE_CANCELLED = 7; + + // An internal server error occurred (HTTP 500). + ERROR_CODE_INTERNAL = 8; + + // The service is temporarily unavailable (HTTP 503). + ERROR_CODE_UNAVAILABLE = 9; +} + +// ErrorDetail provides structured error information. +message ErrorDetail { + // Machine-readable error code. + ErrorCode code = 1; + + // Human-readable error message. + string message = 2; + + // Field that caused the error (for validation errors). + string field = 3; + + // Additional error context as key-value pairs. + map metadata = 4; +} + +// ErrorResponse is the standard error response format for TEA APIs. +message ErrorResponse { + // Primary error code. + ErrorCode code = 1; + + // Human-readable error message. + string message = 2; + + // Detailed error information. + repeated ErrorDetail details = 3; + + // Unique request ID for tracing. + string request_id = 4 [json_name = "requestId"]; + + // Optional documentation URL for this error type. + string documentation_url = 5 [json_name = "documentationUrl"]; +} + +// ============================================================================ +// Timestamp Utilities +// ============================================================================ + +// DateRange represents a range of dates for filtering. +message DateRange { + // Start of the date range (inclusive). + google.protobuf.Timestamp start = 1; + + // End of the date range (exclusive). + google.protobuf.Timestamp end = 2; +} + +// ============================================================================ +// Sorting +// ============================================================================ + +// SortOrder specifies the direction of sorting. +enum SortOrder { + // Unspecified sort order - server default (usually descending by date). + SORT_ORDER_UNSPECIFIED = 0; + + // Ascending order (oldest first, A-Z). + SORT_ORDER_ASC = 1; + + // Descending order (newest first, Z-A). + SORT_ORDER_DESC = 2; +} + +// SortField specifies which field to sort by. +enum SortField { + // Unspecified sort field - server default. + SORT_FIELD_UNSPECIFIED = 0; + + // Sort by creation date. + SORT_FIELD_CREATED_DATE = 1; + + // Sort by release date. + SORT_FIELD_RELEASE_DATE = 2; + + // Sort by version (semantic versioning order). + SORT_FIELD_VERSION = 3; + + // Sort by name (alphabetical). + SORT_FIELD_NAME = 4; +} + +// SortSpec specifies sorting parameters. +message SortSpec { + // Field to sort by. + SortField field = 1; + + // Sort direction. + SortOrder order = 2; +} + +// ============================================================================ +// Health Check (following gRPC health checking protocol) +// ============================================================================ + +// HealthStatus represents the health state of a service. +enum HealthStatus { + // Unknown health status. + HEALTH_STATUS_UNSPECIFIED = 0; + + // Service is healthy and serving requests. + HEALTH_STATUS_SERVING = 1; + + // Service is unhealthy and not serving requests. + HEALTH_STATUS_NOT_SERVING = 2; + + // Service exists but health status unknown. + HEALTH_STATUS_UNKNOWN = 3; +} + +// HealthCheckRequest is used to query service health. +message HealthCheckRequest { + // Service name to check. Empty string checks overall server health. + string service = 1; +} + +// HealthCheckResponse contains the health status. +message HealthCheckResponse { + // Current health status. + HealthStatus status = 1; + + // Optional detailed health information. + map details = 2; +} + +// ============================================================================ +// Deprecation Support +// ============================================================================ + +// DeprecationState represents the lifecycle state of an entity. +enum DeprecationState { + // Unspecified deprecation state. + DEPRECATION_STATE_UNSPECIFIED = 0; + + // Entity is active and supported. + DEPRECATION_STATE_ACTIVE = 1; + + // Entity is deprecated but still operational. + DEPRECATION_STATE_DEPRECATED = 2; + + // Entity is in sunset period and will be removed. + DEPRECATION_STATE_SUNSET = 3; +} + +// Deprecation contains deprecation information for an entity. +message Deprecation { + // Current deprecation state. + DeprecationState state = 1; + + // Human-readable deprecation notice. + string notice = 2 [(buf.validate.field).string.max_len = 2048]; + + // Sunset date when the entity will be removed (if known). + optional google.protobuf.Timestamp sunset_date = 3 [json_name = "sunsetDate"]; + + // UUID of the successor entity (if applicable). + optional string successor_uuid = 4 [(buf.validate.field).string.uuid = true, json_name = "successorUuid"]; +} diff --git a/proto/tea/v1/component.proto b/proto/tea/v1/component.proto new file mode 100644 index 0000000..61c053f --- /dev/null +++ b/proto/tea/v1/component.proto @@ -0,0 +1,334 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.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"; + +// ============================================================================ +// TEA Component +// ============================================================================ + +// Component represents a TEA Component - a component lineage. +// A product release may be constructed with one or multiple TEA Components, +// each with their own set of releases and related artifacts. +// +// Example: "Apache Log4j Core" is a Component with multiple ComponentReleases +// like 2.24.3, 2.24.2, etc. +message Component { + // Unique identifier for this TEA Component. + // Format: UUID v4 or v7. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // Human-readable name of the component. + string name = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 512 + ]; + + // Optional description of the component. + string description = 3 [(buf.validate.field).string.max_len = 4096]; + + // List of identifiers for this component. + // A component may have multiple identifiers (e.g., both CPE and PURL). + repeated Identifier identifiers = 4; + + // Timestamp when this component was created in the TEA system. + google.protobuf.Timestamp created_date = 5 [json_name = "createdDate"]; + + // Timestamp when this component was last modified. + google.protobuf.Timestamp modified_date = 6 [json_name = "modifiedDate"]; + + // Component type classification. + ComponentType component_type = 7 [json_name = "componentType"]; + + // Optional license information for the component. + repeated LicenseInfo licenses = 8; + + // Optional publisher/author information. + string publisher = 9; + + // Optional URL to the component's homepage. + string homepage_url = 10 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "homepageUrl" + ]; + + // Optional URL to the component's VCS repository. + string vcs_url = 11 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "vcsUrl" + ]; + + // Optional deprecation information. + optional Deprecation deprecation = 12; +} + +// ComponentType classifies the type of component. +enum ComponentType { + // Unspecified component type. + COMPONENT_TYPE_UNSPECIFIED = 0; + + // A software application. + COMPONENT_TYPE_APPLICATION = 1; + + // A software framework. + COMPONENT_TYPE_FRAMEWORK = 2; + + // A software library. + COMPONENT_TYPE_LIBRARY = 3; + + // An operating system. + COMPONENT_TYPE_OPERATING_SYSTEM = 4; + + // A physical or virtual device. + COMPONENT_TYPE_DEVICE = 5; + + // A firmware image. + COMPONENT_TYPE_FIRMWARE = 6; + + // A software file (not a library/application). + COMPONENT_TYPE_FILE = 7; + + // A container image (Docker, OCI). + COMPONENT_TYPE_CONTAINER = 8; + + // A platform (cloud service, runtime). + COMPONENT_TYPE_PLATFORM = 9; + + // A machine learning model. + COMPONENT_TYPE_MACHINE_LEARNING_MODEL = 10; + + // A dataset used for ML or other purposes. + COMPONENT_TYPE_DATA = 11; + + // Cryptographic asset (key, certificate). + COMPONENT_TYPE_CRYPTOGRAPHIC_ASSET = 12; +} + +// LicenseInfo contains license information for a component. +message LicenseInfo { + // SPDX license identifier (e.g., "Apache-2.0", "MIT"). + string spdx_id = 1 [json_name = "spdxId"]; + + // License name (if not SPDX-recognized). + string name = 2; + + // URL to the license text. + string url = 3 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE + ]; +} + +// ============================================================================ +// TEA Component Release +// ============================================================================ + +// ComponentRelease represents a specific version of a TEA Component. +// Each release may include multiple distributions and has an associated +// TEA Collection containing security artifacts. +// +// The UUID of a ComponentRelease is identical to the UUID of its associated +// TEA Collection. +message ComponentRelease { + // Unique identifier for this component release. + // This is also the UUID of the associated TEA Collection. + // Format: UUID v4 or v7. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // UUID of the TEA Component this release belongs to. + string component = 2 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // Human-readable version string. + // Can follow any versioning scheme (semver, calver, etc.). + string version = 3 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 256 + ]; + + // Timestamp when this release was created in the TEA system. + google.protobuf.Timestamp created_date = 4 [json_name = "createdDate"]; + + // Upstream release timestamp. + // This is the actual release date, not when it was added to TEA. + google.protobuf.Timestamp release_date = 5 [json_name = "releaseDate"]; + + // Indicates if this is a pre-release (beta, RC, milestone, etc.). + // This flag can be disabled (set to false) after creation, but not enabled. + bool pre_release = 6 [json_name = "preRelease"]; + + // List of identifiers for this release. + repeated Identifier identifiers = 7; + + // List of distributions for this release. + // Distributions capture variations such as architecture, packaging, or + // localization of the same release. + repeated Distribution distributions = 8; + + // Optional deprecation information. + optional Deprecation deprecation = 9; +} + +// ============================================================================ +// Distribution +// ============================================================================ + +// Distribution represents a specific variant of a ComponentRelease. +// For software, each distribution typically corresponds to a different +// digital file delivered to users (e.g., by platform or packaging type). +// For hardware, distributions may reflect differences in packaging, language, +// or other physical attributes. +message Distribution { + // Unique identifier for this distribution type. + // Defined by the producer and used to associate TEA Artifacts. + // Examples: "jar", "tar.gz", "windows-x64.zip", "windows-x64.exe" + string distribution_type = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 128, + (buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9._-]*$", + json_name = "distributionType" + ]; + + // Human-readable description of the distribution. + string description = 2 [(buf.validate.field).string.max_len = 1024]; + + // Identifiers specific to this distribution. + // Example: PURL with type qualifier for specific packaging. + repeated Identifier identifiers = 3; + + // Direct download URL for the distribution. + string url = 4 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE + ]; + + // Direct download URL for the distribution's external signature. + string signature_url = 5 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "signatureUrl" + ]; + + // Checksums for the distribution file. + // At least SHA-256 is recommended. + repeated Checksum checksums = 6; + + // File size in bytes (if known). + optional int64 size_bytes = 7 [json_name = "sizeBytes"]; + + // MIME type of the distribution file. + string mime_type = 8 [json_name = "mimeType"]; +} + +// ============================================================================ +// Request/Response Messages +// ============================================================================ + +// ListComponentsRequest is the request for listing components. +message ListComponentsRequest { + // Pagination parameters. + PageRequest pagination = 1; + + // Optional search query (matches name, description). + string query = 2 [(buf.validate.field).string.max_len = 256]; + + // Filter by component type. + ComponentType component_type = 3 [json_name = "componentType"]; + + // Filter by identifier. + optional Identifier identifier = 4; + + // Optional sort specification. + SortSpec sort = 5; +} + +// ListComponentsResponse is the response for listing components. +message ListComponentsResponse { + // List of components. + repeated Component components = 1; + + // Pagination information. + PageResponse pagination = 2; +} + +// GetComponentRequest is the request for getting a single component. +message GetComponentRequest { + // UUID of the component to retrieve. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// ListComponentReleasesRequest is the request for listing releases of a component. +message ListComponentReleasesRequest { + // UUID of the component. + string component_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "componentUuid" + ]; + + // Pagination parameters. + PageRequest pagination = 2; + + // Include pre-releases in the response. + bool include_pre_releases = 3 [json_name = "includePreReleases"]; + + // Filter by release date range. + DateRange release_date_range = 4 [json_name = "releaseDateRange"]; + + // Optional sort specification. + SortSpec sort = 5; +} + +// ListComponentReleasesResponse is the response for listing component releases. +message ListComponentReleasesResponse { + // List of component releases. + repeated ComponentRelease releases = 1; + + // Pagination information. + PageResponse pagination = 2; +} + +// GetComponentReleaseRequest is the request for getting a single component release. +message GetComponentReleaseRequest { + // UUID of the component release to retrieve. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// SearchByChecksumRequest finds releases by distribution checksum. +message SearchByChecksumRequest { + // Checksum to search for. + Checksum checksum = 1 [(buf.validate.field).required = true]; + + // Pagination parameters. + PageRequest pagination = 2; +} + +// SearchByChecksumResponse contains matching releases. +message SearchByChecksumResponse { + // Matching component releases. + repeated ComponentRelease releases = 1; + + // Pagination information. + PageResponse pagination = 2; +} \ No newline at end of file diff --git a/proto/tea/v1/consumer.proto b/proto/tea/v1/consumer.proto new file mode 100644 index 0000000..1178f82 --- /dev/null +++ b/proto/tea/v1/consumer.proto @@ -0,0 +1,386 @@ +// 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/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"; + +// ============================================================================ +// Consumer Service +// ============================================================================ + +// ConsumerService provides read-only access to TEA transparency artifacts. +// This is the primary API for TEA consumers (customers, auditors, automated +// systems) to discover and retrieve artifacts. +// +// All endpoints support optional authentication. Some endpoints may require +// authentication depending on the server's authorization policy. +// +// This service implements the TEA Consumer API specification. +service ConsumerService { + // ========================================================================== + // Product Operations + // ========================================================================== + + // ListProducts returns a paginated list of available products. + // Products are optional higher-level groupings of product releases. + rpc ListProducts(ListProductsRequest) returns (ListProductsResponse) { + option (google.api.http) = { + get: "/v1/products" + }; + } + + // GetProduct retrieves a single product by UUID. + rpc GetProduct(GetProductRequest) returns (Product) { + option (google.api.http) = { + get: "/v1/products/{uuid}" + }; + } + + // ListProductReleases returns releases for a specific product. + rpc ListProductReleases(ListProductReleasesRequest) returns (ListProductReleasesResponse) { + option (google.api.http) = { + get: "/v1/products/{product_uuid}/releases" + }; + } + + // GetProductRelease retrieves a single product release by UUID. + rpc GetProductRelease(GetProductReleaseRequest) returns (ProductRelease) { + option (google.api.http) = { + get: "/v1/product-releases/{uuid}" + }; + } + + // GetProductReleaseCollection retrieves the collection for a product release. + rpc GetProductReleaseCollection(GetProductReleaseCollectionRequest) returns (Collection) { + option (google.api.http) = { + get: "/v1/product-releases/{uuid}/collection" + }; + } + + // ========================================================================== + // Component Operations + // ========================================================================== + + // ListComponents returns a paginated list of available components. + rpc ListComponents(ListComponentsRequest) returns (ListComponentsResponse) { + option (google.api.http) = { + get: "/v1/components" + }; + } + + // GetComponent retrieves a single component by UUID. + rpc GetComponent(GetComponentRequest) returns (Component) { + option (google.api.http) = { + get: "/v1/components/{uuid}" + }; + } + + // ListComponentReleases returns releases for a specific component. + rpc ListComponentReleases(ListComponentReleasesRequest) returns (ListComponentReleasesResponse) { + option (google.api.http) = { + get: "/v1/components/{component_uuid}/releases" + }; + } + + // GetComponentRelease retrieves a single component release by UUID. + rpc GetComponentRelease(GetComponentReleaseRequest) returns (ComponentRelease) { + option (google.api.http) = { + get: "/v1/component-releases/{uuid}" + }; + } + + // GetComponentReleaseCollection retrieves the collection for a component release. + rpc GetComponentReleaseCollection(GetComponentReleaseCollectionRequest) returns (Collection) { + option (google.api.http) = { + get: "/v1/component-releases/{uuid}/collection" + }; + } + + // ========================================================================== + // Collection Operations + // ========================================================================== + + // GetCollection retrieves the latest version of a collection. + rpc GetCollection(GetCollectionRequest) returns (Collection) { + option (google.api.http) = { + get: "/v1/collections/{uuid}" + }; + } + + // ListCollectionVersions returns all versions of a collection. + rpc ListCollectionVersions(ListCollectionVersionsRequest) returns (ListCollectionVersionsResponse) { + option (google.api.http) = { + get: "/v1/collections/{uuid}/versions" + }; + } + + // GetCollectionVersion retrieves a specific version of a collection. + rpc GetCollectionVersion(GetCollectionVersionRequest) returns (Collection) { + option (google.api.http) = { + get: "/v1/collections/{uuid}/versions/{version}" + }; + } + + // CompareCollectionVersions shows differences between two versions. + rpc CompareCollectionVersions(CompareCollectionVersionsRequest) returns (CompareCollectionVersionsResponse) { + option (google.api.http) = { + get: "/v1/collections/{uuid}/compare" + }; + } + + // ========================================================================== + // Artifact Operations + // ========================================================================== + + // GetArtifact retrieves artifact metadata by UUID. + rpc GetArtifact(GetArtifactRequest) returns (Artifact) { + option (google.api.http) = { + get: "/v1/artifacts/{uuid}" + }; + } + + // GetArtifactContent downloads artifact content. + // Supports conditional requests (If-None-Match) and range requests. + // For large artifacts, consider using the streaming variant. + rpc GetArtifactContent(GetArtifactContentRequest) returns (GetArtifactContentResponse) { + option (google.api.http) = { + get: "/v1/artifacts/{uuid}/content" + }; + } + + // StreamArtifactContent streams artifact content in chunks. + // Recommended for large artifacts. + rpc StreamArtifactContent(GetArtifactContentRequest) returns (stream ArtifactContentChunk) { + option (google.api.http) = { + get: "/v1/artifacts/{uuid}/stream" + }; + } + + // HeadArtifactContent checks artifact status without downloading. + // Returns ETag and Last-Modified for conditional requests. + rpc HeadArtifactContent(HeadArtifactContentRequest) returns (HeadArtifactContentResponse) { + option (google.api.http) = { + // Note: gRPC-Gateway will convert this to HEAD + get: "/v1/artifacts/{uuid}/content/head" + }; + } + + // ========================================================================== + // Search Operations + // ========================================================================== + + // SearchByIdentifier finds entities matching an identifier. + rpc SearchByIdentifier(SearchByIdentifierRequest) returns (SearchByIdentifierResponse) { + option (google.api.http) = { + get: "/v1/search/identifier" + }; + } + + // SearchByChecksum finds releases by distribution checksum. + rpc SearchByChecksum(SearchByChecksumRequest) returns (SearchByChecksumResponse) { + option (google.api.http) = { + get: "/v1/search/checksum" + }; + } + + // SearchArtifacts finds artifacts matching criteria. + rpc SearchArtifacts(SearchArtifactsRequest) returns (SearchArtifactsResponse) { + option (google.api.http) = { + get: "/v1/search/artifacts" + }; + } +} + +// ============================================================================ +// Additional Request/Response Messages +// ============================================================================ + +// GetProductReleaseCollectionRequest is the request for getting a product release's collection. +message GetProductReleaseCollectionRequest { + // UUID of the product release. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Collection version to retrieve (latest if omitted). + optional int32 version = 2; +} + +// GetComponentReleaseCollectionRequest is the request for getting a component release's collection. +message GetComponentReleaseCollectionRequest { + // UUID of the component release. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Collection version to retrieve (latest if omitted). + optional int32 version = 2; +} + +// HeadArtifactContentRequest checks artifact without downloading. +message HeadArtifactContentRequest { + // UUID of the artifact. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Preferred format (MIME type). + string preferred_format = 2 [json_name = "preferredFormat"]; +} + +// HeadArtifactContentResponse contains artifact metadata without content. +message HeadArtifactContentResponse { + // ETag for conditional requests. + string etag = 1; + + // Last modification timestamp. + google.protobuf.Timestamp last_modified = 2 [json_name = "lastModified"]; + + // Content length in bytes. + int64 content_length = 3 [json_name = "contentLength"]; + + // Content MIME type. + string content_type = 4 [json_name = "contentType"]; + + // Whether the artifact accepts range requests. + bool accept_ranges = 5 [json_name = "acceptRanges"]; + + // Checksums for the content. + repeated Checksum checksums = 6; +} + +// SearchByIdentifierRequest finds entities by identifier. +message SearchByIdentifierRequest { + // Identifier to search for. + Identifier identifier = 1 [(buf.validate.field).required = true]; + + // Types of entities to search (empty = all). + repeated EntityType entity_types = 2 [json_name = "entityTypes"]; + + // Pagination parameters. + PageRequest pagination = 3; +} + +// EntityType specifies what kind of entity to search for. +enum EntityType { + // Unspecified entity type. + ENTITY_TYPE_UNSPECIFIED = 0; + + // Search for products. + ENTITY_TYPE_PRODUCT = 1; + + // Search for product releases. + ENTITY_TYPE_PRODUCT_RELEASE = 2; + + // Search for components. + ENTITY_TYPE_COMPONENT = 3; + + // Search for component releases. + ENTITY_TYPE_COMPONENT_RELEASE = 4; + + // Search for artifacts. + ENTITY_TYPE_ARTIFACT = 5; +} + +// SearchByIdentifierResponse contains matching entities. +message SearchByIdentifierResponse { + // Matching products. + repeated Product products = 1; + + // Matching product releases. + repeated ProductRelease product_releases = 2 [json_name = "productReleases"]; + + // Matching components. + repeated Component components = 3; + + // Matching component releases. + repeated ComponentRelease component_releases = 4 [json_name = "componentReleases"]; + + // Pagination information. + PageResponse pagination = 5; +} + +// SearchArtifactsRequest searches for artifacts. +message SearchArtifactsRequest { + // Filter by artifact type. + ArtifactType artifact_type = 1 [json_name = "artifactType"]; + + // Filter by MIME type. + string mime_type = 2 [json_name = "mimeType"]; + + // Filter by subject identifier. + optional Identifier subject_identifier = 3 [json_name = "subjectIdentifier"]; + + // Filter by creation date range. + DateRange created_date_range = 4 [json_name = "createdDateRange"]; + + // Text search query. + string query = 5; + + // Pagination parameters. + PageRequest pagination = 6; + + // Sort specification. + SortSpec sort = 7; +} + +// SearchArtifactsResponse contains matching artifacts. +message SearchArtifactsResponse { + // Matching artifacts. + repeated Artifact artifacts = 1; + + // Pagination information. + PageResponse pagination = 2; +} + +// ============================================================================ +// Batch Operations +// ============================================================================ + +// BatchGetArtifactsRequest retrieves multiple artifacts at once. +message BatchGetArtifactsRequest { + // UUIDs of artifacts to retrieve. + repeated string uuids = 1 [ + (buf.validate.field).repeated.min_items = 1, + (buf.validate.field).repeated.max_items = 100 + ]; +} + +// BatchGetArtifactsResponse contains multiple artifacts. +message BatchGetArtifactsResponse { + // Retrieved artifacts (in request order). + repeated Artifact artifacts = 1; + + // UUIDs that were not found. + repeated string not_found_uuids = 2 [json_name = "notFoundUuids"]; +} + +// BatchGetCollectionsRequest retrieves multiple collections at once. +message BatchGetCollectionsRequest { + // UUIDs of collections to retrieve. + repeated string uuids = 1 [ + (buf.validate.field).repeated.min_items = 1, + (buf.validate.field).repeated.max_items = 50 + ]; + + // Include artifacts in response. + bool include_artifacts = 2 [json_name = "includeArtifacts"]; +} + +// BatchGetCollectionsResponse contains multiple collections. +message BatchGetCollectionsResponse { + // Retrieved collections. + repeated Collection collections = 1; + + // UUIDs that were not found. + repeated string not_found_uuids = 2 [json_name = "notFoundUuids"]; +} diff --git a/proto/tea/v1/discovery.proto b/proto/tea/v1/discovery.proto new file mode 100644 index 0000000..97d9c3f --- /dev/null +++ b/proto/tea/v1/discovery.proto @@ -0,0 +1,360 @@ +// 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/timestamp.proto"; +import "tea/v1/common.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"; + +// ============================================================================ +// Discovery Service +// ============================================================================ + +// DiscoveryService provides TEI (Transparency Exchange Identifier) resolution +// and API endpoint discovery capabilities. +// +// The discovery flow: +// 1. Client has a TEI (e.g., from QR code, invoice, software about box) +// 2. Client extracts domain from TEI and fetches /.well-known/tea +// 3. Client connects to listed endpoint and calls Discover with the TEI +// 4. Server resolves TEI to a ProductRelease UUID +// 5. Client can then use Consumer API to fetch artifacts +service DiscoveryService { + // Discover resolves a TEI to a ProductRelease. + // This is the primary entry point for TEA clients. + // + // The TEI is URL-encoded in the query parameter. + // Example: /v1/discovery?tei=urn%3Atei%3Auuid%3Aexample.com%3A... + rpc Discover(DiscoverRequest) returns (DiscoverResponse) { + option (google.api.http) = { + get: "/v1/discovery" + }; + } + + // GetWellKnown returns the .well-known/tea discovery document. + // This is typically served as a static JSON file, but can be dynamic. + // + // Note: In production, this is served at /.well-known/tea not /v1. + // The gRPC method is provided for completeness and testing. + rpc GetWellKnown(GetWellKnownRequest) returns (WellKnownResponse) { + option (google.api.http) = { + get: "/.well-known/tea" + }; + } + + // Health check for the discovery service. + rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse) { + option (google.api.http) = { + get: "/v1/health" + }; + } + + // GetServerInfo returns metadata about the TEA server. + rpc GetServerInfo(GetServerInfoRequest) returns (ServerInfo) { + option (google.api.http) = { + get: "/v1/info" + }; + } +} + +// ============================================================================ +// TEI Types +// ============================================================================ + +// TeiType specifies the type component of a TEI URN. +enum TeiType { + // Unspecified TEI type. + TEI_TYPE_UNSPECIFIED = 0; + + // UUID-based TEI: urn:tei:uuid:: + TEI_TYPE_UUID = 1; + + // PURL-based TEI: urn:tei:purl:: + TEI_TYPE_PURL = 2; + + // SWID-based TEI: urn:tei:swid:: + TEI_TYPE_SWID = 3; + + // Hash-based TEI: urn:tei:hash::: + TEI_TYPE_HASH = 4; + + // EAN/UPC-based TEI: urn:tei:eanupc:: + TEI_TYPE_EANUPC = 5; + + // GTIN-based TEI: urn:tei:gtin:: + TEI_TYPE_GTIN = 6; + + // ASIN-based TEI: urn:tei:asin:: + TEI_TYPE_ASIN = 7; + + // UDI-based TEI: urn:tei:udi:: + TEI_TYPE_UDI = 8; +} + +// ParsedTei represents a parsed TEI URN structure. +message ParsedTei { + // Original TEI string. + string raw = 1; + + // TEI type component. + TeiType type = 2; + + // Domain name component (used for DNS resolution). + string domain = 3; + + // Unique identifier component. + string unique_id = 4 [json_name = "uniqueId"]; +} + +// ============================================================================ +// Well-Known Discovery +// ============================================================================ + +// GetWellKnownRequest is the request for the .well-known/tea endpoint. +message GetWellKnownRequest { + // No parameters needed. +} + +// WellKnownResponse is the response from /.well-known/tea. +// Conforms to the TEA Well-Known Schema. +message WellKnownResponse { + // Schema version. Currently always 1. + int32 schema_version = 1 [ + (buf.validate.field).int32.const = 1, + json_name = "schemaVersion" + ]; + + // List of available TEA endpoints. + repeated Endpoint endpoints = 2 [(buf.validate.field).repeated.min_items = 1]; +} + +// Endpoint describes a TEA API endpoint. +message Endpoint { + // Base URL of the TEA API endpoint (no trailing slash). + // Example: "https://api.teaexample.com" + string url = 1 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).required = true + ]; + + // Supported TEA API versions for this endpoint. + // Example: ["0.2.0-beta.2", "1.0.0"] + repeated string versions = 2 [(buf.validate.field).repeated.min_items = 1]; + + // Optional priority (0.0 to 1.0). Higher = preferred. + // Default is 1.0. + float priority = 3; +} + +// ============================================================================ +// Discovery Request/Response +// ============================================================================ + +// DiscoverRequest is the request to resolve a TEI. +message DiscoverRequest { + // The TEI to resolve (URL-encoded). + // Example: urn:tei:uuid:cyclonedx.org:d4d9f54a-abcf-11ee-ac79-1a52914d44b1 + string tei = 1 [ + (buf.validate.field).string.min_len = 10, + (buf.validate.field).string.max_len = 2048, + (buf.validate.field).required = true + ]; +} + +// DiscoverResponse contains the result of TEI resolution. +message DiscoverResponse { + // UUID of the resolved ProductRelease. + string product_release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + json_name = "productReleaseUuid" + ]; + + // Base URL of this TEA server. + string server_url = 2 [json_name = "serverUrl"]; + + // Supported API versions on this server. + repeated string supported_versions = 3 [json_name = "supportedVersions"]; + + // The TEI that was resolved. + string tei = 4; + + // Parsed TEI components (for client convenience). + ParsedTei parsed_tei = 5 [json_name = "parsedTei"]; + + // All known TEIs for this ProductRelease. + // Helps clients avoid duplicate entries. + repeated string all_teis = 6 [json_name = "allTeis"]; + + // All known identifiers for this ProductRelease. + repeated Identifier identifiers = 7; + + // Basic ProductRelease metadata. + ProductReleaseMetadata product_release = 8 [json_name = "productRelease"]; + + // Whether authentication is required to access this release's artifacts. + bool authentication_required = 9 [json_name = "authenticationRequired"]; + + // Supported authentication methods (if authentication is required). + repeated AuthMethod auth_methods = 10 [json_name = "authMethods"]; +} + +// ProductReleaseMetadata contains basic info about a ProductRelease +// returned during discovery (before full API access). +message ProductReleaseMetadata { + // UUID of the ProductRelease. + string uuid = 1; + + // Version string. + string version = 2; + + // Product name (if available). + string product_name = 3 [json_name = "productName"]; + + // Vendor name (if available). + string vendor_name = 4 [json_name = "vendorName"]; + + // Release date. + google.protobuf.Timestamp release_date = 5 [json_name = "releaseDate"]; + + // Number of artifacts available. + int32 artifact_count = 6 [json_name = "artifactCount"]; +} + +// ============================================================================ +// Authentication Methods +// ============================================================================ + +// AuthMethod describes a supported authentication method. +enum AuthMethod { + // Unspecified authentication method. + AUTH_METHOD_UNSPECIFIED = 0; + + // HTTP Bearer token authentication. + AUTH_METHOD_BEARER_TOKEN = 1; + + // Mutual TLS with client certificates. + AUTH_METHOD_MTLS = 2; + + // API key authentication. + AUTH_METHOD_API_KEY = 3; + + // OAuth 2.0 authentication. + AUTH_METHOD_OAUTH2 = 4; + + // OpenID Connect authentication. + AUTH_METHOD_OIDC = 5; +} + +// ============================================================================ +// Server Information +// ============================================================================ + +// GetServerInfoRequest is the request for server information. +message GetServerInfoRequest { + // No parameters needed. +} + +// ServerInfo contains metadata about the TEA server. +message ServerInfo { + // Server name/identifier. + string name = 1; + + // Server version. + string version = 2; + + // TEA specification version(s) supported. + repeated string spec_versions = 3 [json_name = "specVersions"]; + + // Server description. + string description = 4; + + // Operator contact information. + string operator_contact = 5 [json_name = "operatorContact"]; + + // Terms of service URL. + string tos_url = 6 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "tosUrl" + ]; + + // Privacy policy URL. + string privacy_url = 7 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "privacyUrl" + ]; + + // Documentation URL. + string documentation_url = 8 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "documentationUrl" + ]; + + // Supported features/capabilities. + ServerCapabilities capabilities = 9; + + // Rate limiting information. + RateLimitInfo rate_limits = 10 [json_name = "rateLimits"]; +} + +// ServerCapabilities describes what features the server supports. +message ServerCapabilities { + // Consumer API is available. + bool consumer_api = 1 [json_name = "consumerApi"]; + + // Publisher API is available. + bool publisher_api = 2 [json_name = "publisherApi"]; + + // Insights/Query API is available. + bool insights_api = 3 [json_name = "insightsApi"]; + + // Collection signing is supported. + bool collection_signing = 4 [json_name = "collectionSigning"]; + + // Sigstore integration is available. + bool sigstore_integration = 5 [json_name = "sigstoreIntegration"]; + + // Search by checksum is supported. + bool checksum_search = 6 [json_name = "checksumSearch"]; + + // CEL query language is supported. + bool cel_queries = 7 [json_name = "celQueries"]; + + // Streaming downloads are supported. + bool streaming_downloads = 8 [json_name = "streamingDownloads"]; + + // Supported artifact types. + repeated ArtifactTypeSupport artifact_types = 9 [json_name = "artifactTypes"]; +} + +// ArtifactTypeSupport describes support for an artifact type. +message ArtifactTypeSupport { + // Artifact type. + string type = 1; + + // Supported formats (MIME types). + repeated string formats = 2; +} + +// RateLimitInfo provides rate limiting details. +message RateLimitInfo { + // Requests per minute for unauthenticated clients. + int32 unauthenticated_rpm = 1 [json_name = "unauthenticatedRpm"]; + + // Requests per minute for authenticated clients. + int32 authenticated_rpm = 2 [json_name = "authenticatedRpm"]; + + // Maximum download bandwidth (bytes per second) per client. + int64 max_bandwidth_bps = 3 [json_name = "maxBandwidthBps"]; +} diff --git a/proto/tea/v1/product.proto b/proto/tea/v1/product.proto new file mode 100644 index 0000000..25840d8 --- /dev/null +++ b/proto/tea/v1/product.proto @@ -0,0 +1,302 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.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"; + +// ============================================================================ +// TEA Product +// ============================================================================ + +// Product represents a TEA Product - an optional higher-level object that +// groups multiple Product Releases for a product line or family. +// +// Products can be discovered and browsed; releases are accessed via the +// product releases endpoint. +// +// Example: "Apache Log4j 2" is a Product containing multiple ProductReleases +// like 2.24.3, 2.24.2, etc. +message Product { + // Unique identifier for this TEA Product. + // Format: UUID v4 or v7. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // Human-readable name of the product. + string name = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 512 + ]; + + // Optional description of the product. + string description = 3 [(buf.validate.field).string.max_len = 4096]; + + // List of identifiers for this product. + // A product may have multiple identifiers (e.g., both CPE and PURL). + repeated Identifier identifiers = 4; + + // Vendor/publisher information. + Vendor vendor = 5; + + // Timestamp when this product was created in the TEA system. + google.protobuf.Timestamp created_date = 6 [json_name = "createdDate"]; + + // Timestamp when this product was last modified. + google.protobuf.Timestamp modified_date = 7 [json_name = "modifiedDate"]; + + // Optional URL to the product's homepage. + string homepage_url = 8 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "homepageUrl" + ]; + + // Optional URL to the product's documentation. + string documentation_url = 9 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "documentationUrl" + ]; + + // Optional URL to the product's VCS repository. + string vcs_url = 10 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE, + json_name = "vcsUrl" + ]; + + // Optional deprecation information. + optional Deprecation deprecation = 11; +} + +// Vendor represents a product vendor or publisher. +message Vendor { + // Vendor name. + string name = 1 [(buf.validate.field).string.max_len = 512]; + + // Optional vendor UUID (if registered in this TEA instance). + optional string uuid = 2 [(buf.validate.field).string.uuid = true]; + + // Optional vendor URL. + string url = 3 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE + ]; + + // Optional contact information. + repeated Contact contacts = 4; +} + +// Contact represents contact information for a vendor or organization. +message Contact { + // Contact name. + string name = 1 [(buf.validate.field).string.max_len = 256]; + + // Contact email address. + string email = 2 [ + (buf.validate.field).string.email = true, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE + ]; + + // Contact phone number. + string phone = 3 [(buf.validate.field).string.max_len = 64]; +} + +// ============================================================================ +// TEA Product Release +// ============================================================================ + +// ProductRelease represents a specific versioned release of a TEA Product. +// It is the primary resolvable entity via TEI and the entry point for +// discovery of included components and related collections of security artifacts. +// +// A ProductRelease is what a customer acquires or downloads - hardware and/or +// software. It can be a bundle of many digital devices or software applications. +message ProductRelease { + // Unique identifier for this product release. + // Format: UUID v4 or v7. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // UUID of the TEA Product this release belongs to. + // Optional - a release may exist without a parent product. + optional string product = 2 [(buf.validate.field).string.uuid = true]; + + // Human-readable version string of the product release. + // Can follow any versioning scheme (semver, calver, etc.). + string version = 3 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 256 + ]; + + // Timestamp when this product release was created in the TEA system. + google.protobuf.Timestamp created_date = 4 [json_name = "createdDate"]; + + // Upstream product release timestamp. + // This is the actual release date, not when it was added to TEA. + google.protobuf.Timestamp release_date = 5 [json_name = "releaseDate"]; + + // Indicates if this is a pre-release (beta, RC, milestone, etc.). + // Pre-releases may have limited support or stability guarantees. + // This flag can be disabled (set to false) after creation, but not enabled. + bool pre_release = 6 [json_name = "preRelease"]; + + // List of identifiers for this product release. + // Must include at least one TEI for discovery. + repeated Identifier identifiers = 7; + + // List of TEA Components included in this product release. + // For composed products, this lists all bundled components. + repeated ComponentRef components = 8; + + // Optional lifecycle status (if CLE is integrated). + LifecycleStatus lifecycle_status = 9 [json_name = "lifecycleStatus"]; + + // Optional deprecation information. + optional Deprecation deprecation = 10; +} + +// ComponentRef is a reference to a TEA Component included in a ProductRelease. +message ComponentRef { + // UUID of the TEA Component. + string uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true + ]; + + // Optional UUID of a specific TEA Component Release to pin. + // If omitted, the product release may include any/multiple releases + // of the referenced component. + optional string release = 2 [(buf.validate.field).string.uuid = true]; +} + +// ============================================================================ +// Lifecycle Status (CLE Integration) +// ============================================================================ + +// LifecyclePhase represents the current phase in a product's lifecycle. +// Based on OWASP Common Lifecycle Enumeration (CLE). +enum LifecyclePhase { + // Unspecified phase. + LIFECYCLE_PHASE_UNSPECIFIED = 0; + + // Product is in active development. + LIFECYCLE_PHASE_DEVELOPMENT = 1; + + // Product is released and actively maintained. + LIFECYCLE_PHASE_ACTIVE = 2; + + // Product is still supported but no new features. + LIFECYCLE_PHASE_MAINTENANCE = 3; + + // Product has reached end of life. + LIFECYCLE_PHASE_END_OF_LIFE = 4; + + // Product has been deprecated in favor of a successor. + LIFECYCLE_PHASE_DEPRECATED = 5; + + // Product has been superseded by another product. + LIFECYCLE_PHASE_SUPERSEDED = 6; +} + +// LifecycleStatus contains lifecycle information for a product or release. +message LifecycleStatus { + // Current lifecycle phase. + LifecyclePhase phase = 1; + + // End of active support date (if known). + google.protobuf.Timestamp end_of_support = 2 [json_name = "endOfSupport"]; + + // End of life date (if known). + google.protobuf.Timestamp end_of_life = 3 [json_name = "endOfLife"]; + + // UUID of the successor product/release (if superseded). + optional string successor_uuid = 4 [json_name = "successorUuid"]; + + // Human-readable lifecycle notes. + string notes = 5; +} + +// ============================================================================ +// Request/Response Messages +// ============================================================================ + +// ListProductsRequest is the request for listing products. +message ListProductsRequest { + // Pagination parameters. + PageRequest pagination = 1; + + // Optional filter by vendor UUID. + optional string vendor_uuid = 2 [(buf.validate.field).string.uuid = true, json_name = "vendorUuid"]; + + // Optional search query (matches name, description). + string query = 3 [(buf.validate.field).string.max_len = 256]; + + // Optional sort specification. + SortSpec sort = 4; + + // Filter by identifier. + optional Identifier identifier = 5; +} + +// ListProductsResponse is the response for listing products. +message ListProductsResponse { + // List of products. + repeated Product products = 1; + + // Pagination information. + PageResponse pagination = 2; +} + +// GetProductRequest is the request for getting a single product. +message GetProductRequest { + // UUID of the product to retrieve. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// ListProductReleasesRequest is the request for listing releases of a product. +message ListProductReleasesRequest { + // UUID of the product. + string product_uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true, json_name = "productUuid"]; + + // Pagination parameters. + PageRequest pagination = 2; + + // Include pre-releases in the response. + bool include_pre_releases = 3 [json_name = "includePreReleases"]; + + // Filter by release date range. + DateRange release_date_range = 4 [json_name = "releaseDateRange"]; + + // Optional sort specification. + SortSpec sort = 5; +} + +// ListProductReleasesResponse is the response for listing product releases. +message ListProductReleasesResponse { + // List of product releases. + repeated ProductRelease releases = 1; + + // Pagination information. + PageResponse pagination = 2; +} + +// GetProductReleaseRequest is the request for getting a single product release. +message GetProductReleaseRequest { + // UUID of the product release to retrieve. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} \ No newline at end of file diff --git a/schemas/json/artifact/artifact-format.schema.json b/schemas/json/artifact/artifact-format.schema.json new file mode 100644 index 0000000..c676f0d --- /dev/null +++ b/schemas/json/artifact/artifact-format.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/artifact-format.schema.json", + "type": "object", + "title": "TEA Artifact Format", + "description": "Represents a specific encoding/format of an artifact.", + "properties": { + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "MIME type of the document." + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "Human-readable description of this format." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Direct download URL for the artifact in this format." + }, + "signatureUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL for a detached digital signature." + }, + "checksums": { + "type": "array", + "items": { + "$ref": "../common/checksum.schema.json" + }, + "minItems": 1, + "description": "Checksums for integrity verification." + }, + "sizeBytes": { + "type": "integer", + "minimum": 0, + "description": "File size in bytes." + }, + "encoding": { + "type": "string", + "description": "Encoding of the content (e.g., 'utf-8', 'base64')." + }, + "specVersion": { + "type": "string", + "description": "Specification version (for typed artifacts)." + } + }, + "required": [ + "mimeType", + "url", + "checksums" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/artifact/artifact.schema.json b/schemas/json/artifact/artifact.schema.json new file mode 100644 index 0000000..29ba648 --- /dev/null +++ b/schemas/json/artifact/artifact.schema.json @@ -0,0 +1,116 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/artifact.schema.json", + "type": "object", + "title": "TEA Artifact", + "description": "Represents a TEA Artifact - a security-related document or file linked to a component release.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this TEA Artifact." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Human-readable name for the artifact." + }, + "type": { + "type": "string", + "enum": [ + "ARTIFACT_TYPE_UNSPECIFIED", + "ARTIFACT_TYPE_ATTESTATION", + "ARTIFACT_TYPE_BOM", + "ARTIFACT_TYPE_BUILD_META", + "ARTIFACT_TYPE_CERTIFICATION", + "ARTIFACT_TYPE_FORMULATION", + "ARTIFACT_TYPE_LICENSE", + "ARTIFACT_TYPE_RELEASE_NOTES", + "ARTIFACT_TYPE_SECURITY_TXT", + "ARTIFACT_TYPE_THREAT_MODEL", + "ARTIFACT_TYPE_VULNERABILITIES", + "ARTIFACT_TYPE_CLE", + "ARTIFACT_TYPE_CDXA", + "ARTIFACT_TYPE_CBOM", + "ARTIFACT_TYPE_MODEL_CARD", + "ARTIFACT_TYPE_STATIC_ANALYSIS", + "ARTIFACT_TYPE_DYNAMIC_ANALYSIS", + "ARTIFACT_TYPE_PENTEST_REPORT", + "ARTIFACT_TYPE_RISK_ASSESSMENT", + "ARTIFACT_TYPE_POAM", + "ARTIFACT_TYPE_QUALITY_METRICS", + "ARTIFACT_TYPE_HARNESS", + "ARTIFACT_TYPE_CONFORMANCE", + "ARTIFACT_TYPE_OTHER" + ], + "description": "Type of the artifact." + }, + "componentDistributions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Distribution types this artifact applies to." + }, + "formats": { + "type": "array", + "items": { + "$ref": "artifact-format.schema.json" + }, + "minItems": 1, + "description": "Available formats for this artifact." + }, + "createdDate": { + "type": "string", + "format": "date-time", + "description": "Timestamp when this artifact was created in the TEA system." + }, + "description": { + "type": "string", + "maxLength": 4096, + "description": "Optional description of the artifact." + }, + "subject": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "SUBJECT_TYPE_UNSPECIFIED", + "SUBJECT_TYPE_COMPONENT", + "SUBJECT_TYPE_PRODUCT", + "SUBJECT_TYPE_SERVICE", + "SUBJECT_TYPE_ORGANIZATION", + "SUBJECT_TYPE_BUILD" + ], + "description": "Type of subject." + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "../common/identifier.schema.json" + }, + "description": "Identifiers for the subject." + }, + "name": { + "type": "string", + "description": "Human-readable name of the subject." + }, + "version": { + "type": "string", + "description": "Version of the subject (if applicable)." + } + }, + "additionalProperties": false, + "description": "Subject of the artifact (what it describes)." + } + }, + "required": [ + "uuid", + "name", + "type", + "formats" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/collection/collection.schema.json b/schemas/json/collection/collection.schema.json new file mode 100644 index 0000000..7b194ec --- /dev/null +++ b/schemas/json/collection/collection.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/collection.schema.json", + "type": "object", + "title": "TEA Collection", + "description": "Represents a TEA Collection - a versioned set of artifacts associated with a release.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this TEA Collection." + }, + "version": { + "type": "integer", + "minimum": 1, + "description": "Version number of this collection." + }, + "date": { + "type": "string", + "format": "date-time", + "description": "Date when this collection version was created." + }, + "belongsTo": { + "type": "string", + "enum": [ + "COLLECTION_SCOPE_UNSPECIFIED", + "COLLECTION_SCOPE_RELEASE", + "COLLECTION_SCOPE_PRODUCT_RELEASE" + ], + "description": "Scope of the collection (component release or product release)." + }, + "updateReason": { + "$ref": "update-reason.schema.json" + }, + "artifacts": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "UUIDs of artifacts in this collection." + }, + "createdDate": { + "type": "string", + "format": "date-time", + "description": "Timestamp when this collection was created." + }, + "modifiedDate": { + "type": "string", + "format": "date-time", + "description": "Timestamp when this collection was last modified." + } + }, + "required": [ + "uuid", + "version", + "belongsTo", + "updateReason" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/collection/update-reason.schema.json b/schemas/json/collection/update-reason.schema.json new file mode 100644 index 0000000..25db210 --- /dev/null +++ b/schemas/json/collection/update-reason.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/update-reason.schema.json", + "type": "string", + "title": "TEA Update Reason", + "description": "Reason for updating a collection version.", + "enum": [ + "UPDATE_REASON_TYPE_UNSPECIFIED", + "UPDATE_REASON_TYPE_INITIAL_RELEASE", + "UPDATE_REASON_TYPE_VEX_UPDATED", + "UPDATE_REASON_TYPE_ARTIFACT_UPDATED", + "UPDATE_REASON_TYPE_ARTIFACT_REMOVED", + "UPDATE_REASON_TYPE_ARTIFACT_ADDED" + ] +} \ No newline at end of file diff --git a/schemas/json/common/checksum.schema.json b/schemas/json/common/checksum.schema.json new file mode 100644 index 0000000..1382c4d --- /dev/null +++ b/schemas/json/common/checksum.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/checksum.schema.json", + "type": "object", + "title": "TEA Checksum", + "description": "Represents a cryptographic hash of content for integrity verification.", + "properties": { + "algType": { + "type": "string", + "enum": [ + "CHECKSUM_ALGORITHM_UNSPECIFIED", + "CHECKSUM_ALGORITHM_MD5", + "CHECKSUM_ALGORITHM_SHA1", + "CHECKSUM_ALGORITHM_SHA256", + "CHECKSUM_ALGORITHM_SHA384", + "CHECKSUM_ALGORITHM_SHA512", + "CHECKSUM_ALGORITHM_SHA3_256", + "CHECKSUM_ALGORITHM_SHA3_384", + "CHECKSUM_ALGORITHM_SHA3_512", + "CHECKSUM_ALGORITHM_BLAKE2B_256", + "CHECKSUM_ALGORITHM_BLAKE2B_384", + "CHECKSUM_ALGORITHM_BLAKE2B_512", + "CHECKSUM_ALGORITHM_BLAKE3" + ], + "description": "Algorithm used to compute the checksum." + }, + "algValue": { + "type": "string", + "pattern": "^[a-f0-9]+$", + "minLength": 32, + "maxLength": 256, + "description": "Hexadecimal-encoded checksum value (lowercase)." + } + }, + "required": [ + "algType", + "algValue" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/common/error.schema.json b/schemas/json/common/error.schema.json new file mode 100644 index 0000000..2615c65 --- /dev/null +++ b/schemas/json/common/error.schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/error.schema.json", + "type": "object", + "title": "TEA Error", + "description": "Standard error response format for TEA APIs.", + "properties": { + "code": { + "type": "string", + "enum": [ + "ERROR_CODE_UNSPECIFIED", + "ERROR_CODE_INVALID_ARGUMENT", + "ERROR_CODE_UNAUTHENTICATED", + "ERROR_CODE_PERMISSION_DENIED", + "ERROR_CODE_NOT_FOUND", + "ERROR_CODE_ALREADY_EXISTS", + "ERROR_CODE_RESOURCE_EXHAUSTED", + "ERROR_CODE_CANCELLED", + "ERROR_CODE_INTERNAL", + "ERROR_CODE_UNAVAILABLE" + ], + "description": "Machine-readable error code." + }, + "message": { + "type": "string", + "description": "Human-readable error message." + }, + "field": { + "type": "string", + "description": "Field that caused the error (for validation errors)." + }, + "details": { + "type": "array", + "items": { + "$ref": "#/$defs/ErrorDetail" + }, + "description": "Detailed error information." + }, + "requestId": { + "type": "string", + "description": "Unique request ID for tracing." + }, + "documentationUrl": { + "type": "string", + "format": "uri", + "description": "Optional documentation URL for this error type." + } + }, + "required": [ + "code", + "message" + ], + "additionalProperties": false, + "$defs": { + "ErrorDetail": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "ERROR_CODE_UNSPECIFIED", + "ERROR_CODE_INVALID_ARGUMENT", + "ERROR_CODE_UNAUTHENTICATED", + "ERROR_CODE_PERMISSION_DENIED", + "ERROR_CODE_NOT_FOUND", + "ERROR_CODE_ALREADY_EXISTS", + "ERROR_CODE_RESOURCE_EXHAUSTED", + "ERROR_CODE_CANCELLED", + "ERROR_CODE_INTERNAL", + "ERROR_CODE_UNAVAILABLE" + ] + }, + "message": { + "type": "string" + }, + "field": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/schemas/json/common/identifier.schema.json b/schemas/json/common/identifier.schema.json new file mode 100644 index 0000000..b20165b --- /dev/null +++ b/schemas/json/common/identifier.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/identifier.schema.json", + "type": "object", + "title": "TEA Identifier", + "description": "Represents a typed identifier for a TEA entity. Identifiers are immutable and globally unique within their type namespace.", + "properties": { + "idType": { + "type": "string", + "enum": [ + "IDENTIFIER_TYPE_UNSPECIFIED", + "IDENTIFIER_TYPE_TEI", + "IDENTIFIER_TYPE_PURL", + "IDENTIFIER_TYPE_CPE", + "IDENTIFIER_TYPE_SWID", + "IDENTIFIER_TYPE_GAV", + "IDENTIFIER_TYPE_GTIN", + "IDENTIFIER_TYPE_GMN", + "IDENTIFIER_TYPE_UDI", + "IDENTIFIER_TYPE_ASIN", + "IDENTIFIER_TYPE_HASH", + "IDENTIFIER_TYPE_CONFORMANCE" + ], + "description": "Type of the identifier." + }, + "idValue": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "Value of the identifier in its canonical string form. Must conform to the format specification of the identifier type." + } + }, + "required": ["idType", "idValue"], + "additionalProperties": false +} diff --git a/schemas/json/common/pagination.schema.json b/schemas/json/common/pagination.schema.json new file mode 100644 index 0000000..4acbd8a --- /dev/null +++ b/schemas/json/common/pagination.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/pagination.schema.json", + "type": "object", + "title": "TEA Pagination", + "description": "Pagination parameters for list operations.", + "properties": { + "pageSize": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Maximum number of items to return per page. Server may return fewer items. Default is 20, maximum is 100." + }, + "pageToken": { + "type": "string", + "description": "Opaque token for fetching the next page of results. Obtained from PageResponse.next_page_token of a previous request. Omit for the first page." + }, + "nextPageToken": { + "type": "string", + "description": "Token to retrieve the next page of results. Empty if there are no more results." + }, + "totalCount": { + "type": "integer", + "minimum": 0, + "description": "Total number of items across all pages (if known). May be omitted if the total is expensive to compute." + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/component/component-release.schema.json b/schemas/json/component/component-release.schema.json new file mode 100644 index 0000000..97b0e85 --- /dev/null +++ b/schemas/json/component/component-release.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/component-release.schema.json", + "type": "object", + "title": "TEA Component Release", + "description": "Represents a TEA Component Release - a specific version of a component with associated distributions.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this TEA Component Release." + }, + "componentUuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the parent component." + }, + "version": { + "type": "string", + "minLength": 1, + "description": "Version string of this release." + }, + "releaseDate": { + "type": "string", + "format": "date-time", + "description": "Release date." + }, + "preRelease": { + "type": "boolean", + "description": "Whether this is a pre-release version." + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "../common/identifier.schema.json" + }, + "description": "List of identifiers for this release." + }, + "distributions": { + "type": "array", + "items": { + "$ref": "distribution.schema.json" + }, + "description": "Available distributions for this release." + } + }, + "required": [ + "uuid", + "componentUuid", + "version" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/component/component.schema.json b/schemas/json/component/component.schema.json new file mode 100644 index 0000000..66df2ae --- /dev/null +++ b/schemas/json/component/component.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/component.schema.json", + "type": "object", + "title": "TEA Component", + "description": "Represents a TEA Component - a software or hardware element that can be released independently.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this TEA Component." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Human-readable name of the component." + }, + "description": { + "type": "string", + "maxLength": 4096, + "description": "Optional description of the component." + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "../common/identifier.schema.json" + }, + "description": "List of identifiers for this component." + }, + "componentType": { + "type": "string", + "enum": [ + "COMPONENT_TYPE_UNSPECIFIED", + "COMPONENT_TYPE_APPLICATION", + "COMPONENT_TYPE_FRAMEWORK", + "COMPONENT_TYPE_LIBRARY", + "COMPONENT_TYPE_CONTAINER", + "COMPONENT_TYPE_OPERATING_SYSTEM", + "COMPONENT_TYPE_DEVICE", + "COMPONENT_TYPE_FILE", + "COMPONENT_TYPE_FIRMWARE", + "COMPONENT_TYPE_OTHER" + ], + "description": "Type of the component." + }, + "licenses": { + "type": "array", + "items": { + "$ref": "license.schema.json" + }, + "description": "License information for the component." + }, + "publisher": { + "type": "string", + "description": "Name of the component publisher." + }, + "homepageUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL to the component's homepage." + }, + "vcsUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL to the component's VCS repository." + } + }, + "required": [ + "uuid", + "name" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/component/distribution.schema.json b/schemas/json/component/distribution.schema.json new file mode 100644 index 0000000..2cf1294 --- /dev/null +++ b/schemas/json/component/distribution.schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/distribution.schema.json", + "type": "object", + "title": "TEA Distribution", + "description": "Represents a distribution channel or package format for a component release.", + "properties": { + "distributionType": { + "type": "string", + "minLength": 1, + "description": "Type of distribution (e.g., 'deb', 'rpm', 'maven', 'npm')." + }, + "description": { + "type": "string", + "description": "Human-readable description of this distribution." + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "../common/identifier.schema.json" + }, + "description": "Identifiers for this distribution." + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL where this distribution can be downloaded." + }, + "signatureUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL for a detached digital signature." + }, + "checksums": { + "type": "array", + "items": { + "$ref": "../common/checksum.schema.json" + }, + "minItems": 1, + "description": "Checksums for integrity verification." + } + }, + "required": [ + "distributionType", + "checksums" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/component/license.schema.json b/schemas/json/component/license.schema.json new file mode 100644 index 0000000..46c0074 --- /dev/null +++ b/schemas/json/component/license.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/license.schema.json", + "type": "object", + "title": "TEA License Info", + "description": "Represents license information for a component.", + "properties": { + "licenseType": { + "type": "string", + "enum": [ + "LICENSE_TYPE_UNSPECIFIED", + "LICENSE_TYPE_SPDX", + "LICENSE_TYPE_OTHER" + ], + "description": "Type of license identifier." + }, + "licenseId": { + "type": "string", + "minLength": 1, + "description": "License identifier (SPDX license ID or custom)." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Optional URL to the full license text." + } + }, + "required": [ + "licenseType", + "licenseId" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/discovery/discovery-response.schema.json b/schemas/json/discovery/discovery-response.schema.json new file mode 100644 index 0000000..eab8cc9 --- /dev/null +++ b/schemas/json/discovery/discovery-response.schema.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/discovery-response.schema.json", + "type": "object", + "title": "TEA Discovery Response", + "description": "Response from TEI discovery and well-known endpoint queries.", + "oneOf": [ + { + "properties": { + "productReleaseUuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the product release that the TEI resolves to." + } + }, + "additionalProperties": false + }, + { + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "Schema version for the TEA .well-known discovery document. Currently always 1." + }, + "endpoints": { + "type": "array", + "description": "List of available TEA service endpoints and their supported versions.", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Base URL of the TEA API endpoint (no trailing slash)." + }, + "versions": { + "type": "array", + "description": "Supported TEA API versions for this endpoint. Use with the /v{version} prefix when constructing requests.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^\\d+\\.\\d+(?:\\.\\d+)?(?:-[0-9A-Za-z.-]+)?$", + "examples": [ + "0.1.0-beta.1", + "0.2.0-beta.2", + "1.0.0" + ], + "description": "TEA OpenAPI Spec Version identifier, conforms to SemVer 2.0 (https://semver.org/)." + } + }, + "priority": { + "type": "integer", + "minimum": 0, + "description": "Optional priority for load balancing or client selection. Lower values have higher priority." + }, + "description": { + "type": "string", + "description": "Optional human-readable description of this endpoint." + } + }, + "required": [ + "url", + "versions" + ], + "additionalProperties": false + } + } + }, + "required": [ + "schemaVersion", + "endpoints" + ], + "additionalProperties": false + } + ] +} \ No newline at end of file diff --git a/schemas/json/product/component-ref.schema.json b/schemas/json/product/component-ref.schema.json new file mode 100644 index 0000000..3870ed8 --- /dev/null +++ b/schemas/json/product/component-ref.schema.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/component-ref.schema.json", + "type": "object", + "title": "TEA Component Reference", + "description": "Reference to a component and its release within a product release.", + "properties": { + "componentUuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the referenced component." + }, + "releaseUuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the referenced component release." + } + }, + "required": [ + "componentUuid", + "releaseUuid" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/product/contact.schema.json b/schemas/json/product/contact.schema.json new file mode 100644 index 0000000..d38caf8 --- /dev/null +++ b/schemas/json/product/contact.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/contact.schema.json", + "type": "object", + "title": "TEA Contact", + "description": "Represents contact information for a vendor or organization.", + "properties": { + "type": { + "type": "string", + "enum": [ + "CONTACT_TYPE_UNSPECIFIED", + "CONTACT_TYPE_EMAIL", + "CONTACT_TYPE_PHONE", + "CONTACT_TYPE_URL", + "CONTACT_TYPE_OTHER" + ], + "description": "Type of contact information." + }, + "value": { + "type": "string", + "minLength": 1, + "description": "Contact value (email address, phone number, URL, etc.)." + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/product/product-release.schema.json b/schemas/json/product/product-release.schema.json new file mode 100644 index 0000000..33a9dea --- /dev/null +++ b/schemas/json/product/product-release.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/product-release.schema.json", + "type": "object", + "title": "TEA Product Release", + "description": "Represents a TEA Product Release - a specific version of a product with associated components.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this TEA Product Release." + }, + "productUuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the parent product." + }, + "version": { + "type": "string", + "minLength": 1, + "description": "Version string of this release." + }, + "releaseDate": { + "type": "string", + "format": "date-time", + "description": "Release date." + }, + "preRelease": { + "type": "boolean", + "description": "Whether this is a pre-release version." + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "../common/identifier.schema.json" + }, + "description": "List of identifiers for this release." + }, + "components": { + "type": "array", + "items": { + "$ref": "component-ref.schema.json" + }, + "description": "References to components included in this product release." + } + }, + "required": [ + "uuid", + "version" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/product/product.schema.json b/schemas/json/product/product.schema.json new file mode 100644 index 0000000..040bd77 --- /dev/null +++ b/schemas/json/product/product.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/product.schema.json", + "type": "object", + "title": "TEA Product", + "description": "Represents a TEA Product - an optional higher-level object that groups multiple Product Releases for a product line or family.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for this TEA Product." + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "Human-readable name of the product." + }, + "description": { + "type": "string", + "maxLength": 4096, + "description": "Optional description of the product." + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "../common/identifier.schema.json" + }, + "description": "List of identifiers for this product." + }, + "vendor": { + "$ref": "vendor.schema.json" + }, + "createdDate": { + "type": "string", + "format": "date-time", + "description": "Timestamp when this product was created in the TEA system." + }, + "modifiedDate": { + "type": "string", + "format": "date-time", + "description": "Timestamp when this product was last modified." + }, + "homepageUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL to the product's homepage." + }, + "documentationUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL to the product's documentation." + }, + "vcsUrl": { + "type": "string", + "format": "uri", + "description": "Optional URL to the product's VCS repository." + } + }, + "required": [ + "uuid", + "name" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/json/product/vendor.schema.json b/schemas/json/product/vendor.schema.json new file mode 100644 index 0000000..64f5547 --- /dev/null +++ b/schemas/json/product/vendor.schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://cyclonedx.org/schema/tea/v1/vendor.schema.json", + "type": "object", + "title": "TEA Vendor", + "description": "Represents a product vendor or publisher.", + "properties": { + "name": { + "type": "string", + "maxLength": 512, + "description": "Vendor name." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Optional vendor UUID (if registered in this TEA instance)." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Optional vendor URL." + }, + "contacts": { + "type": "array", + "items": { + "$ref": "contact.schema.json" + }, + "description": "Optional contact information." + } + }, + "additionalProperties": false +} \ No newline at end of file