From 46d87961cddf32f4cea4f1263a29130b42bd51a7 Mon Sep 17 00:00:00 2001 From: Mohamed Chorfa Date: Sun, 29 Mar 2026 18:58:58 -0400 Subject: [PATCH 1/4] spec: add TEA discovery, proto, and schema drafts Signed-off-by: Mohamed Chorfa --- api-flow/consumer.md | 43 +- doc/authentication.md | 97 ++ doc/authorization.md | 98 ++ doc/migration-guide.md | 178 +++ doc/tea-requirements.md | 12 +- doc/tea-usecases.md | 14 +- doc/tea_consumer_flow.excalidraw | 1092 ++++++++++++++++ doc/tea_data_model.excalidraw | 1015 +++++++++++++++ doc/tea_discovery_security.excalidraw | 1103 +++++++++++++++++ doc/tea_publisher_flow.excalidraw | 948 ++++++++++++++ doc/versioning.md | 146 +++ graph-schema.json | 73 ++ proto/.gitignore | 16 + proto/Makefile | 114 ++ proto/buf.gen.yaml | 30 + proto/buf.lock | 12 + proto/buf.yaml | 46 + proto/tea/v1/artifact.proto | 364 ++++++ proto/tea/v1/collection.proto | 316 +++++ proto/tea/v1/common.proto | 371 ++++++ proto/tea/v1/component.proto | 334 +++++ proto/tea/v1/consumer.proto | 386 ++++++ proto/tea/v1/discovery.proto | 360 ++++++ proto/tea/v1/insights.proto | 791 ++++++++++++ proto/tea/v1/product.proto | 302 +++++ proto/tea/v1/publisher.proto | 792 ++++++++++++ .../json/artifact/artifact-format.schema.json | 57 + schemas/json/artifact/artifact.schema.json | 116 ++ .../json/collection/collection.schema.json | 61 + .../json/collection/update-reason.schema.json | 15 + schemas/json/common/checksum.schema.json | 40 + schemas/json/common/error.schema.json | 89 ++ schemas/json/common/identifier.schema.json | 35 + schemas/json/common/pagination.schema.json | 29 + .../component/component-release.schema.json | 53 + schemas/json/component/component.schema.json | 74 ++ .../json/component/distribution.schema.json | 48 + schemas/json/component/license.schema.json | 33 + .../discovery/discovery-response.schema.json | 77 ++ .../json/product/component-ref.schema.json | 24 + schemas/json/product/contact.schema.json | 30 + .../json/product/product-release.schema.json | 52 + schemas/json/product/product.schema.json | 65 + schemas/json/product/vendor.schema.json | 32 + 44 files changed, 9969 insertions(+), 14 deletions(-) create mode 100644 doc/authentication.md create mode 100644 doc/authorization.md create mode 100644 doc/migration-guide.md create mode 100644 doc/tea_consumer_flow.excalidraw create mode 100644 doc/tea_data_model.excalidraw create mode 100644 doc/tea_discovery_security.excalidraw create mode 100644 doc/tea_publisher_flow.excalidraw create mode 100644 doc/versioning.md create mode 100644 graph-schema.json create mode 100644 proto/.gitignore create mode 100644 proto/Makefile create mode 100644 proto/buf.gen.yaml create mode 100644 proto/buf.lock create mode 100644 proto/buf.yaml create mode 100644 proto/tea/v1/artifact.proto create mode 100644 proto/tea/v1/collection.proto create mode 100644 proto/tea/v1/common.proto create mode 100644 proto/tea/v1/component.proto create mode 100644 proto/tea/v1/consumer.proto create mode 100644 proto/tea/v1/discovery.proto create mode 100644 proto/tea/v1/insights.proto create mode 100644 proto/tea/v1/product.proto create mode 100644 proto/tea/v1/publisher.proto create mode 100644 schemas/json/artifact/artifact-format.schema.json create mode 100644 schemas/json/artifact/artifact.schema.json create mode 100644 schemas/json/collection/collection.schema.json create mode 100644 schemas/json/collection/update-reason.schema.json create mode 100644 schemas/json/common/checksum.schema.json create mode 100644 schemas/json/common/error.schema.json create mode 100644 schemas/json/common/identifier.schema.json create mode 100644 schemas/json/common/pagination.schema.json create mode 100644 schemas/json/component/component-release.schema.json create mode 100644 schemas/json/component/component.schema.json create mode 100644 schemas/json/component/distribution.schema.json create mode 100644 schemas/json/component/license.schema.json create mode 100644 schemas/json/discovery/discovery-response.schema.json create mode 100644 schemas/json/product/component-ref.schema.json create mode 100644 schemas/json/product/contact.schema.json create mode 100644 schemas/json/product/product-release.schema.json create mode 100644 schemas/json/product/product.schema.json create mode 100644 schemas/json/product/vendor.schema.json diff --git a/api-flow/consumer.md b/api-flow/consumer.md index 1bd7137..3451ecb 100644 --- a/api-flow/consumer.md +++ b/api-flow/consumer.md @@ -23,8 +23,18 @@ Component Release) to find the list of artefacts for the particular Product Rele ## API flow based on TEI discovery ```mermaid - --- +config: + sequence: + diagramMarginX: 60 + diagramMarginY: 40 + actorFontSize: 20 + actorFontWeight: bold + noteFontSize: 18 + theme: neo dark + layout: elk + look: neo + title: TEA consumer flow --- sequenceDiagram @@ -60,7 +70,6 @@ sequenceDiagram user ->> tea_component_release: Obtain latest collections tea_component_release -->> user: List of TEA Artifacts end - ``` ## API flow based on direct access to API @@ -70,6 +79,16 @@ In this case, the client wants to search for a specific product release using th ```mermaid --- +config: + sequence: + diagramMarginX: 60 + diagramMarginY: 40 + actorFontSize: 20 + actorFontWeight: bold + noteFontSize: 18 + theme: neo dark + layout: elk + look: neo title: TEA client flow with search --- @@ -109,6 +128,16 @@ for a release. ```mermaid --- +config: + sequence: + diagramMarginX: 60 + diagramMarginY: 40 + actorFontSize: 20 + actorFontWeight: bold + noteFontSize: 18 + theme: neo dark + layout: elk + look: neo title: TEA client flow with direct query for release --- @@ -137,6 +166,16 @@ another query is done to get reason for update and new collection list of artefa ```mermaid --- +config: + sequence: + diagramMarginX: 60 + diagramMarginY: 40 + actorFontSize: 20 + actorFontWeight: bold + noteFontSize: 18 + theme: neo + layout: elk + look: neo title: TEA client collection query --- diff --git a/doc/authentication.md b/doc/authentication.md new file mode 100644 index 0000000..13f5bf6 --- /dev/null +++ b/doc/authentication.md @@ -0,0 +1,97 @@ +# Authentication + +The Transparency Exchange API (TEA) supports two primary authentication mechanisms: Bearer Token authentication and Mutual TLS (mTLS) authentication. Implementations MUST support at least one of these mechanisms, and SHOULD support both for maximum compatibility. + +## Bearer Token Authentication + +Bearer token authentication uses JSON Web Tokens (JWTs) issued by an external identity provider. This is the recommended authentication mechanism for most use cases. + +### Token Acquisition + +Tokens are acquired out-of-band from the TEA server. The exact process depends on the service provider's identity management system, but typically involves: + +1. User or service authenticates with the provider's portal or API +2. Provider issues a short-lived JWT (recommended: < 1 hour) +3. Client includes the token in API requests + +### Token Format + +Tokens MUST be valid JWTs conforming to RFC 7519. The token payload SHOULD include: + +- `iss`: Issuer identifier +- `sub`: Subject (user or service identifier) +- `aud`: Audience (TEA server identifier) +- `exp`: Expiration time +- `iat`: Issued at time +- `scope`: Space-separated list of authorized scopes + +### Request Format + +Include the token in the `Authorization` header: + +``` +Authorization: Bearer +``` + +### Token Validation + +Servers MUST validate: + +- Token signature using the issuer's public key +- Token expiration (`exp` claim) +- Token audience (`aud` claim) +- Required scopes for the operation + +## Mutual TLS Authentication + +Mutual TLS authentication uses client certificates for mutual authentication between client and server. + +### Certificate Requirements + +- Client certificates MUST use ECDSA P-384 or Ed25519 algorithms +- Certificates MUST be issued by a trusted Certificate Authority (CA) +- Certificate Subject Alternative Name (SAN) MUST include the client identifier + +### TLS Configuration + +- Minimum TLS version: 1.3 +- Server MUST request client certificates +- Server MUST validate certificate chain +- Client MUST present valid certificate + +### Authorization Mapping + +The client certificate's subject or SAN is mapped to TEA identities and scopes through server-side configuration. + +## Authentication Flow + +```mermaid +sequenceDiagram + participant Client + participant TEA Server + participant IdP + + alt Bearer Token + Client->>IdP: Acquire token + IdP-->>Client: JWT token + Client->>TEA Server: Request + Authorization: Bearer + TEA Server->>TEA Server: Validate token + else mTLS + Client->>TEA Server: TLS handshake with client cert + TEA Server->>TEA Server: Validate certificate + end + + TEA Server-->>Client: Authenticated response +``` + +## Error Responses + +- `401 Unauthorized`: Missing or invalid credentials +- `403 Forbidden`: Valid credentials but insufficient permissions + +## Security Considerations + +- Tokens SHOULD be short-lived (< 1 hour) +- mTLS certificates SHOULD have short validity periods +- Implement token revocation mechanisms +- Log authentication failures for security monitoring diff --git a/doc/authorization.md b/doc/authorization.md new file mode 100644 index 0000000..a8a317f --- /dev/null +++ b/doc/authorization.md @@ -0,0 +1,98 @@ +# Authorization + +The Transparency Exchange API (TEA) uses a scope-based authorization model. Permissions are granted through scopes assigned to authenticated identities. Scopes follow a hierarchical structure with read operations generally requiring fewer permissions than write operations. + +## Scope Model + +Scopes are granted to identities during authentication. Bearer tokens include scopes in the `scope` claim as a space-separated list. mTLS certificates are mapped to scopes through server configuration. + +### Scope Hierarchy + +``` +SCOPE_ADMIN_FULL (admin) +├── SCOPE_PUBLISHER_* (publisher) +│ ├── SCOPE_PUBLISHER_PRODUCTS_WRITE +│ ├── SCOPE_PUBLISHER_COMPONENTS_WRITE +│ ├── SCOPE_PUBLISHER_RELEASES_WRITE +│ ├── SCOPE_PUBLISHER_ARTIFACTS_WRITE +│ └── SCOPE_PUBLISHER_COLLECTIONS_WRITE +└── SCOPE_CONSUMER_* (consumer) + ├── SCOPE_CONSUMER_PRODUCTS_READ + ├── SCOPE_CONSUMER_COMPONENTS_READ + ├── SCOPE_CONSUMER_COLLECTIONS_READ + ├── SCOPE_CONSUMER_ARTIFACTS_READ + ├── SCOPE_CONSUMER_ARTIFACTS_DOWNLOAD + └── SCOPE_CONSUMER_INSIGHTS_QUERY +``` + +## Consumer Scopes + +### Read Operations + +- `SCOPE_CONSUMER_PRODUCTS_READ`: List and retrieve product metadata +- `SCOPE_CONSUMER_COMPONENTS_READ`: List and retrieve component metadata +- `SCOPE_CONSUMER_COLLECTIONS_READ`: Access collection metadata and versions +- `SCOPE_CONSUMER_ARTIFACTS_READ`: Retrieve artifact metadata +- `SCOPE_CONSUMER_ARTIFACTS_DOWNLOAD`: Download artifact content +- `SCOPE_CONSUMER_INSIGHTS_QUERY`: Execute CEL-based queries and searches + +## Publisher Scopes + +### Write Operations + +- `SCOPE_PUBLISHER_PRODUCTS_WRITE`: Create, update, delete products +- `SCOPE_PUBLISHER_COMPONENTS_WRITE`: Create, update, delete components +- `SCOPE_PUBLISHER_RELEASES_WRITE`: Create, update product/component releases +- `SCOPE_PUBLISHER_ARTIFACTS_WRITE`: Upload and delete artifacts +- `SCOPE_PUBLISHER_COLLECTIONS_WRITE`: Create and update collections + +## Admin Scopes + +### Administrative Operations + +- `SCOPE_ADMIN_FULL`: Full administrative access including user management, system configuration, and audit functions + +## Authorization Logic + +### API Endpoint Requirements + +| Endpoint | Required Scopes | +| ----------------------------- | ----------------------------------- | +| `GET /.well-known/tea` | None (public) | +| `GET /v1/discovery` | None (public) | +| `GET /v1/products*` | `SCOPE_CONSUMER_PRODUCTS_READ` | +| `GET /v1/components*` | `SCOPE_CONSUMER_COMPONENTS_READ` | +| `GET /v1/collections*` | `SCOPE_CONSUMER_COLLECTIONS_READ` | +| `GET /v1/artifacts*` | `SCOPE_CONSUMER_ARTIFACTS_READ` | +| `GET /v1/artifacts/*/content` | `SCOPE_CONSUMER_ARTIFACTS_DOWNLOAD` | +| `POST /v1/insights/*` | `SCOPE_CONSUMER_INSIGHTS_QUERY` | +| `POST /v1/publisher/*` | Publisher scopes as above | + +### Scope Validation + +Servers MUST validate that the authenticated identity possesses all required scopes for the requested operation. Missing scopes result in `403 Forbidden` responses. + +### Scope Granularity + +Scopes are intentionally coarse-grained to simplify implementation. Fine-grained access control (e.g., per-object permissions) is not supported in the base specification but may be implemented as extensions. + +## Authorization Flow + +```mermaid +flowchart TD + A[Request] --> B[Authenticate] + B --> C{Valid Identity?} + C -->|No| D[401 Unauthorized] + C -->|Yes| E[Extract Scopes] + E --> F{Required Scopes?} + F -->|No| G[403 Forbidden] + F -->|Yes| H[Process Request] + H --> I[Response] +``` + +## Security Considerations + +- Implement least privilege: grant only necessary scopes +- Regularly rotate credentials and review scope assignments +- Audit authorization decisions for compliance +- Consider scope expiration for temporary access diff --git a/doc/migration-guide.md b/doc/migration-guide.md new file mode 100644 index 0000000..1a622fd --- /dev/null +++ b/doc/migration-guide.md @@ -0,0 +1,178 @@ +# Migration Guide + +This guide provides instructions for migrating between versions of the Transparency Exchange API (TEA). Follow these steps carefully to ensure compatibility and minimize downtime. + +## Version Compatibility Matrix + +| From Version | To Version | Migration Path | Breaking Changes | +| ------------ | ---------- | ----------------------- | --------------------- | +| 0.1.x | 0.2.x | Direct | None | +| 0.2.x | 0.3.x | Direct | None | +| 0.3.x | 1.0.0 | Direct | None (beta to stable) | +| 1.0.x | 1.1.x | Direct | None | +| 1.x.x | 2.0.0 | Review breaking changes | Yes | + +## General Migration Steps + +### 1. Review Release Notes + +Before migrating, carefully review the release notes for the target version: + +- New features and endpoints +- Deprecated functionality +- Required configuration changes +- Known issues and workarounds + +### 2. Update Dependencies + +Update TEA client libraries and dependencies to compatible versions: + +```bash +# Example for different languages +npm update @cyclonedx/tea-client +pip install --upgrade cyclonedx-tea +cargo update cyclonedx-tea +``` + +### 3. Update Configuration + +Review and update configuration files: + +```yaml +# config.yaml +api: + version: "1.0.0" # Update to target version + baseUrl: "https://api.example.com/tea/v1" +``` + +### 4. Update Code + +Modify client code for any API changes: + +```javascript +// Before (v0.3.x) +const client = new TeaClient({ + baseUrl: 'https://api.example.com/tea' +}); + +// After (v1.0.0) +const client = new TeaClient({ + baseUrl: 'https://api.example.com/tea/v1', + version: '1.0.0' +}); +``` + +### 5. Test Migration + +Test the migration in a staging environment: + +```bash +# Run integration tests +npm test +pytest tests/ +cargo test +``` + +### 6. Deploy Gradually + +Use canary deployments or feature flags: + +```bash +# Deploy to 10% of traffic first +kubectl set image deployment/tea-client tea-client:v1.0.0 +kubectl rollout status deployment/tea-client +``` + +## Version-Specific Migrations + +### Migrating from 0.3.x to 1.0.0 + +#### API Changes + +- No breaking changes - this is a stability release +- All beta features are now stable +- Improved error messages and validation + +#### Client Changes + +```typescript +// Update version specification +const client = new TeaClient({ + version: '1.0.0' // Explicitly specify version +}); +``` + +#### Server Changes + +- Update server configuration for stable endpoints +- Review authentication settings +- Update monitoring and logging + +### Migrating from 1.0.x to 1.1.x + +#### New Features + +- Enhanced insights API +- Improved pagination support +- Additional artifact types + +#### Backward Compatibility + +- All existing clients continue to work +- New features are opt-in + +#### Optional Updates + +```python +# Use new insights features +insights = client.insights.query("component.name == 'openssl'") +``` + +## Troubleshooting + +### Common Issues + +#### Authentication Failures + +- Verify token scopes are correct for v1 endpoints +- Check mTLS certificate validity +- Confirm issuer configuration + +#### Version Negotiation + +- Ensure client specifies correct API version +- Check server version support +- Review well-known endpoint configuration + +#### Performance Issues + +- Monitor rate limits after migration +- Check for N+1 query problems +- Optimize batch operations + +### Rollback Plan + +Always prepare a rollback strategy: + +```bash +# Quick rollback command +kubectl rollout undo deployment/tea-client +``` + +## Support + +For migration assistance: + +- Review the [TEA documentation](https://github.com/CycloneDX/transparency-exchange-api) +- Check the [GitHub issues](https://github.com/CycloneDX/transparency-exchange-api/issues) for known issues +- Contact the TEA working group for complex migrations + +## Post-Migration Validation + +After migration, validate: + +- [ ] All existing functionality works +- [ ] New features are accessible +- [ ] Performance meets requirements +- [ ] Monitoring and alerting are configured +- [ ] Documentation is updated diff --git a/doc/tea-requirements.md b/doc/tea-requirements.md index c60a1e6..2f8b070 100644 --- a/doc/tea-requirements.md +++ b/doc/tea-requirements.md @@ -1,6 +1,7 @@ # TEA Requirements ## Repository discovery + Based on an identifier a repository URL needs to be found. The identifier can be: - PURL @@ -34,6 +35,9 @@ Collections are OPTIONAL. - VDR - Vulnerability Disclosure Report - VEX - Vulnerability Exploitability eXchange - CDXA - Attestation +- ML-BOM - Machine Learning Bill of Material (profile of SBOM) +- DATA-BOM - Data Bill of Material (profile of SBOM) +- AI-BOM - Artificial Intelligence Bill of Material (profile of SBOM) Authn/Authz MUST be supported @@ -61,7 +65,7 @@ Authn/Authz MUST be supported ## Artefact Publishing -The API MUST provide a way to publish an artefact, either standalone or to a collection. +The API MUST provide a way to publish an artefact, either standalone or to a collection. The detection of duplicate artefacts with the same identity MUST be handled and prevented. Authn/Authz MUST be supported @@ -85,11 +89,11 @@ PURL, CPE, SWID, GAV, GTIN, and GMN. For example: -- Return the identity of all BOMs that have a vulnerable version of Apache Log4J: +- Return the identity of all BOMs that have a vulnerable version of Apache Log4J: `pkg:maven/org.apache.logging.log4j/log4j-core@2.10.0` -The API MUST provide a way to search for the metadata component across all available BOMs. -The API SHOULD support multiple identity formats including PURL, CPE, SWID, GAV, GTIN, and GMN. +The API MUST provide a way to search for the metadata component across all available BOMs. +The API SHOULD support multiple identity formats including PURL, CPE, SWID, GAV, GTIN, and GMN. For example: - Return the identity of all artefacts that describe `cpe:/a:acme:commerce_suite:1.0`. diff --git a/doc/tea-usecases.md b/doc/tea-usecases.md index bd3b702..916a6a9 100644 --- a/doc/tea-usecases.md +++ b/doc/tea-usecases.md @@ -6,10 +6,10 @@ a..." The use cases are divided in two categories: -* Use cases for __customers__ (end-users, manufacturers) to find a repository with +* Use cases for __customers__ (end-users, manufacturers) to find a repository with Transparency artefacts for a single unit purchased * Use cases where there are different __products__ - * This applies after discovery where we need to handle various things a customer may + * This applies after discovery where we need to handle various things a customer may buy as a single unit ## Customer focused use cases @@ -18,18 +18,17 @@ The use cases are divided in two categories: As a consumer that has an SBOM for a product, I want to be able to retrieve VEX and VDR files automatically both for current and old versions of the software. In the SBOM the product is identified by a PURL or other means (CPE, …) - ### C2: Consumer: Automation based on product name/identifier As a consumer, I want to download artefacts for a product based on known data. A combination of manufacturer, product name, vendor product ID, EAN bar code or other unique identifier. -After discovering the base repository URL I want to be able to find a specific +After discovering the base repository URL I want to be able to find a specific product variant and version. If the consumer is a business, then the procurement process may include delivery of an SBOM with proper identifiers and possibly URLs or identifiers in another document, which may bootstrap the discovery process in a more exact way than in the case of buying a product in a retail market. Alice bought a gadget at the gadget store that contains a full Linux system. Where and how will she find the SBOM and VEX for the gadget? -### C3: Consumer: Artefact retrieval +### C3: Consumer: Artefact retrieval As a consumer, I want to retrieve one or more supply chain artefacts for the products that I have access to, possibly through licensing or other means. As a consumer, I should be able to retrieve all source artefacts such as xBOMs, VDR/VEX, CDXA, and CLE. @@ -42,8 +41,8 @@ A CLE captures all lifecycle events over time, however, there is a need to retri As a consumer, I want the ability to simply ask the API questions rather than having to download, process, and analyze raw supply chain artefacts on my own systems. Common questions should be -provided by the API by default along with the ability to query for more complex answers using -the Common Expression Language (CEL). +provided by the API by default along with the ability to query for more complex answers using +the Common Expression Language (CEL). _NOTE_: Project Hyades (Dependency-Track v5) already implements CEL with the CycloneDX object model and has proven that this approach works for complex queries. @@ -82,7 +81,6 @@ They need to make an assessment before starting tests and possible production us The can either use the Debian package, the Alpine Linux Package or build a binary themselves from source code. How can they find the transparency exchange data sets? - ### O1: Open Source project The hatturl open source project publish a library and a server side software on Github. This is diff --git a/doc/tea_consumer_flow.excalidraw b/doc/tea_consumer_flow.excalidraw new file mode 100644 index 0000000..221943a --- /dev/null +++ b/doc/tea_consumer_flow.excalidraw @@ -0,0 +1,1092 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "version": 2, + "versionNonce": 623532812, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 313667311, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "text", + "id": "t_seq", + "x": 300, + "y": 850, + "text": "TEA Consumer & Discovery Flow", + "fontSize": 28, + "strokeColor": "#1e1e1e", + "width": 446.6, + "height": 35, + "baseline": 31, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "version": 2, + "versionNonce": 288208189, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 508448315, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "uHead2_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "uHead2", + "x": 50, + "y": 950, + "width": 120, + "height": 40, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#4a9eed", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "uHead2_text", + "x": 66, + "y": 960, + "width": 88, + "height": 20, + "text": "TEA Client", + "fontSize": 16, + "fontFamily": 1, + "containerId": "uHead2", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 709554373, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 442489299, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 16 + }, + { + "version": 2, + "versionNonce": 166791788, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 326732043, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "uLine2", + "x": 110, + "y": 990, + "width": 0, + "height": 550, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 550 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 442817095, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 45220990, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "ellipse", + "id": "uh2", + "x": 100, + "y": 900, + "width": 20, + "height": 20, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeColor": "#4a9eed", + "strokeWidth": 2 + }, + { + "version": 2, + "versionNonce": 479732483, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 329452988, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "ub2", + "x": 100, + "y": 922, + "width": 20, + "height": 20, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#4a9eed", + "strokeWidth": 2 + }, + { + "version": 2, + "versionNonce": 367400102, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 499749119, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "dHead_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "dHead", + "x": 300, + "y": 950, + "width": 160, + "height": 40, + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#f59e0b", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "dHead_text", + "x": 291.45, + "y": 961.25, + "width": 177.10000000000002, + "height": 17.5, + "text": "Discovery (.well-known)", + "fontSize": 14, + "fontFamily": 1, + "containerId": "dHead", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 836560960, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 268053522, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 165355887, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 225359599, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "dLine", + "x": 380, + "y": 990, + "width": 0, + "height": 550, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 550 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 434646783, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 963980987, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "prHead_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "prHead", + "x": 550, + "y": 950, + "width": 160, + "height": 40, + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#22c55e", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "prHead_text", + "x": 556.85, + "y": 961.25, + "width": 146.3, + "height": 17.5, + "text": "TEA Product Release", + "fontSize": 14, + "fontFamily": 1, + "containerId": "prHead", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 532673844, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 416354069, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 546612311, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 12876854, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "prLine", + "x": 630, + "y": 990, + "width": 0, + "height": 550, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 550 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 986414963, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 60246222, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "crHead_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "crHead", + "x": 800, + "y": 950, + "width": 180, + "height": 40, + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#8b5cf6", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "crHead_text", + "x": 809.15, + "y": 961.25, + "width": 161.70000000000002, + "height": 17.5, + "text": "TEA Component Release", + "fontSize": 14, + "fontFamily": 1, + "containerId": "crHead", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 678700252, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 159387056, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 628557341, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 604831108, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "crLine", + "x": 890, + "y": 990, + "width": 0, + "height": 550, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 550 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 30546574, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 135177463, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq1_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq1", + "x": 110, + "y": 1020, + "width": 270, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 270, + 0 + ] + ], + "strokeColor": "#f59e0b", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq1_text", + "x": 156.45, + "y": 1011.25, + "width": 177.10000000000002, + "height": 17.5, + "text": "1. GET /.well-known/tea", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq1", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 349482594, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 789890610, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 847461378, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 173622756, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq2_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq2", + "x": 380, + "y": 1060, + "width": -270, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -270, + 0 + ] + ], + "strokeColor": "#f59e0b", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq2_text", + "x": 152.6, + "y": 1051.25, + "width": 184.8, + "height": 17.5, + "text": "2. Returns API endpoints", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq2", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 110092748, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 31118657, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 255758566, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 900025800, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq3_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq3", + "x": 110, + "y": 1110, + "width": 270, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 270, + 0 + ] + ], + "strokeColor": "#f59e0b", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq3_text", + "x": 152.6, + "y": 1101.25, + "width": 184.8, + "height": 17.5, + "text": "3. Call Discovery w/ TEI", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq3", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 329326862, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 811770140, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 26311709, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 127687205, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq4_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq4", + "x": 380, + "y": 1150, + "width": -270, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -270, + 0 + ] + ], + "strokeColor": "#f59e0b", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq4_text", + "x": 137.2, + "y": 1141.25, + "width": 215.60000000000002, + "height": 17.5, + "text": "4. Return Product Release(s)", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq4", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 158861047, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 536938846, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 625818849, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 962926849, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq5_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq5", + "x": 110, + "y": 1200, + "width": 520, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 520, + 0 + ] + ], + "strokeColor": "#22c55e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq5_text", + "x": 281.45, + "y": 1191.25, + "width": 177.10000000000002, + "height": 17.5, + "text": "5. Resolve Prod Release", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq5", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 437731164, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 921428612, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 870790990, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 93929788, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq6_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq6", + "x": 630, + "y": 1240, + "width": -520, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -520, + 0 + ] + ], + "strokeColor": "#22c55e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq6_text", + "x": 262.2, + "y": 1231.25, + "width": 215.60000000000002, + "height": 17.5, + "text": "6. Return Component Releases", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq6", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 613806409, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 71366738, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 63362911, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 126503006, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq7_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq7", + "x": 110, + "y": 1290, + "width": 780, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 780, + 0 + ] + ], + "strokeColor": "#8b5cf6", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq7_text", + "x": 311.35, + "y": 1281.25, + "width": 377.3, + "height": 17.5, + "text": "7. For each Component Release, obtain collections", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq7", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 338008809, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 781177126, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 108508532, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 215234389, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "seq8_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "seq8", + "x": 890, + "y": 1330, + "width": -780, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -780, + 0 + ] + ], + "strokeColor": "#8b5cf6", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "seq8_text", + "x": 411.45, + "y": 1321.25, + "width": 177.10000000000002, + "height": 17.5, + "text": "8. Return TEA Artifacts", + "fontSize": 14, + "fontFamily": 1, + "containerId": "seq8", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 78058391, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 889122579, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "currentItemStrokeColor": "#1e1e1e", + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemStrokeStyle": "solid", + "currentItemRoughness": 1, + "currentItemOpacity": 100, + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemTextAlign": "left", + "currentItemStrokeSharpness": "round" + } +} \ No newline at end of file diff --git a/doc/tea_data_model.excalidraw b/doc/tea_data_model.excalidraw new file mode 100644 index 0000000..bb60eae --- /dev/null +++ b/doc/tea_data_model.excalidraw @@ -0,0 +1,1015 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "version": 2, + "versionNonce": 167281445, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 866940117, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "text", + "id": "title", + "x": 300, + "y": 20, + "text": "Transparency Exchange API (TEA) Data Model", + "fontSize": 28, + "strokeColor": "#1e1e1e", + "width": 646.8000000000001, + "height": 35, + "baseline": 31, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "version": 2, + "versionNonce": 290557472, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 785009667, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "tei_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "tei", + "x": 50, + "y": 200, + "width": 220, + "height": 80, + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#f59e0b" + }, + { + "type": "text", + "id": "tei_text", + "x": 76.39999999999999, + "y": 220, + "width": 167.20000000000002, + "height": 40, + "text": "Discovery (TEI)\nResolves to Release", + "fontSize": 16, + "fontFamily": 1, + "containerId": "tei", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 238103836, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 276140534, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 36 + }, + { + "version": 2, + "versionNonce": 345925228, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 178738737, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "product_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "product", + "x": 400, + "y": 100, + "width": 200, + "height": 80, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#4a9eed" + }, + { + "type": "text", + "id": "product_text", + "x": 438.4, + "y": 120, + "width": 123.20000000000002, + "height": 40, + "text": "TEA Product\n(Product Line)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "product", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 318599718, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 576954118, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 36 + }, + { + "version": 2, + "versionNonce": 717272336, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 12733120, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "prod_rel_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "prod_rel", + "x": 400, + "y": 300, + "width": 200, + "height": 80, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#4a9eed" + }, + { + "type": "text", + "id": "prod_rel_text", + "x": 416.4, + "y": 320, + "width": 167.20000000000002, + "height": 40, + "text": "TEA Product Release\n(Primary Entry)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "prod_rel", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 559444216, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 645638008, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 36 + }, + { + "version": 2, + "versionNonce": 235904703, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 734388130, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_tei_strel_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_tei_strel", + "x": 270, + "y": 240, + "width": 130, + "height": 60, + "points": [ + [ + 0, + 0 + ], + [ + 130, + 60 + ] + ], + "strokeColor": "#f59e0b", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "tei", + "fixedPoint": [ + 1, + 0.5 + ] + }, + "endBinding": { + "elementId": "prod_rel", + "fixedPoint": [ + 0, + 0.5 + ] + }, + "startArrowhead": null + }, + { + "type": "text", + "id": "a_tei_strel_text", + "x": 292.65, + "y": 261.25, + "width": 84.7, + "height": 17.5, + "text": "resolves to", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_tei_strel", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 751043349, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 227857383, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 697299633, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 710202603, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_prod_rel_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_prod_rel", + "x": 500, + "y": 180, + "width": 0, + "height": 120, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 120 + ] + ], + "strokeColor": "#4a9eed", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "product", + "fixedPoint": [ + 0.5, + 1 + ] + }, + "endBinding": { + "elementId": "prod_rel", + "fixedPoint": [ + 0.5, + 0 + ] + }, + "startArrowhead": null + }, + { + "type": "text", + "id": "a_prod_rel_text", + "x": 453.8, + "y": 231.25, + "width": 92.4, + "height": 17.5, + "text": "has releases", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_prod_rel", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 122921120, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 554857509, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 232196529, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 449554058, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "component_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "component", + "x": 800, + "y": 100, + "width": 200, + "height": 80, + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#8b5cf6" + }, + { + "type": "text", + "id": "component_text", + "x": 842.8, + "y": 120, + "width": 114.4, + "height": 40, + "text": "TEA Component\n(Lineage)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "component", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 674237119, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 422435724, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 36 + }, + { + "version": 2, + "versionNonce": 931136153, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 147374708, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "comp_rel_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "comp_rel", + "x": 800, + "y": 300, + "width": 240, + "height": 80, + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#8b5cf6" + }, + { + "type": "text", + "id": "comp_rel_text", + "x": 812.2, + "y": 322.5, + "width": 215.60000000000002, + "height": 35, + "text": "TEA Component Release\n(/component/{uuid}/releases)", + "fontSize": 14, + "fontFamily": 1, + "containerId": "comp_rel", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 878891488, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 23290723, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 31 + }, + { + "version": 2, + "versionNonce": 370736448, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 401196270, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_comp_rel_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_comp_rel", + "x": 900, + "y": 180, + "width": 0, + "height": 120, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 120 + ] + ], + "strokeColor": "#8b5cf6", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "component", + "fixedPoint": [ + 0.5, + 1 + ] + }, + "endBinding": { + "elementId": "comp_rel", + "fixedPoint": [ + 0.5, + 0 + ] + }, + "startArrowhead": null + }, + { + "type": "text", + "id": "a_comp_rel_text", + "x": 853.8, + "y": 231.25, + "width": 92.4, + "height": 17.5, + "text": "has releases", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_comp_rel", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 598949984, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 312229238, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 113491510, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 429136782, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "collection_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "collection", + "x": 600, + "y": 500, + "width": 220, + "height": 80, + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#22c55e" + }, + { + "type": "text", + "id": "collection_text", + "x": 639.6, + "y": 520, + "width": 140.8, + "height": 40, + "text": "TEA Collection\n(Versioned list)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "collection", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 584982859, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 587906769, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 36 + }, + { + "version": 2, + "versionNonce": 499529419, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 761705895, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_prel_coll_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_prel_coll", + "x": 500, + "y": 380, + "width": 100, + "height": 160, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 160 + ] + ], + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "prod_rel", + "fixedPoint": [ + 0.5, + 1 + ] + }, + "endBinding": { + "elementId": "collection", + "fixedPoint": [ + 0, + 0.5 + ] + }, + "startArrowhead": null + }, + { + "type": "text", + "id": "a_prel_coll_text", + "x": 538.45, + "y": 451.25, + "width": 23.1, + "height": 17.5, + "text": "has", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_prel_coll", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 338494172, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 166154473, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 835357745, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 947892553, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_crel_coll_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_crel_coll", + "x": 900, + "y": 380, + "width": -80, + "height": 160, + "points": [ + [ + 0, + 0 + ], + [ + -80, + 160 + ] + ], + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "comp_rel", + "fixedPoint": [ + 0.5, + 1 + ] + }, + "endBinding": { + "elementId": "collection", + "fixedPoint": [ + 1, + 0.5 + ] + }, + "startArrowhead": null + }, + { + "type": "text", + "id": "a_crel_coll_text", + "x": 848.45, + "y": 451.25, + "width": 23.1, + "height": 17.5, + "text": "has", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_crel_coll", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 625958386, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 497177979, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 441589794, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 464410241, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "artifact_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "rectangle", + "id": "artifact", + "x": 600, + "y": 700, + "width": 220, + "height": 80, + "backgroundColor": "#eebefa", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#ec4899" + }, + { + "type": "text", + "id": "artifact_text", + "x": 613.2, + "y": 720, + "width": 193.60000000000002, + "height": 40, + "text": "TEA Artifact\n(xBOM, VEX, CDXA, CLE)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "artifact", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 3658297, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 886159966, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 36 + }, + { + "version": 2, + "versionNonce": 64546817, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 412898613, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_coll_art_text", + "type": "text" + } + ], + "updated": 1771600797567, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_coll_art", + "x": 710, + "y": 580, + "width": 0, + "height": 120, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 120 + ] + ], + "strokeColor": "#22c55e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "collection", + "fixedPoint": [ + 0.5, + 1 + ] + }, + "endBinding": { + "elementId": "artifact", + "fixedPoint": [ + 0.5, + 0 + ] + }, + "startArrowhead": null + }, + { + "type": "text", + "id": "a_coll_art_text", + "x": 679.2, + "y": 631.25, + "width": 61.60000000000001, + "height": 17.5, + "text": "contains", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_coll_art", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 107192323, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 817296938, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797567, + "link": null, + "locked": false, + "baseline": 13.5 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "currentItemStrokeColor": "#1e1e1e", + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemStrokeStyle": "solid", + "currentItemRoughness": 1, + "currentItemOpacity": 100, + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemTextAlign": "left", + "currentItemStrokeSharpness": "round" + } +} \ No newline at end of file diff --git a/doc/tea_discovery_security.excalidraw b/doc/tea_discovery_security.excalidraw new file mode 100644 index 0000000..c45f23b --- /dev/null +++ b/doc/tea_discovery_security.excalidraw @@ -0,0 +1,1103 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "version": 3, + "versionNonce": 835041064, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 737075209, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "text", + "id": "t_disc_title", + "x": 300, + "y": 20, + "text": "TEA Discovery & Security Architecture", + "fontSize": 28, + "strokeColor": "#1e1e1e", + "width": 569.8000000000001, + "height": 35, + "baseline": 31, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "index": "a0", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": { + "type": 2 + }, + "containerId": null, + "originalText": "TEA Discovery & Security Architecture", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 149, + "versionNonce": 786606632, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 105464567, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "tei_struct_text", + "type": "text" + } + ], + "updated": 1771601605550, + "link": null, + "locked": false, + "type": "rectangle", + "id": "tei_struct", + "x": 49.9765625, + "y": 100, + "width": 450.0234375, + "height": 300.3515625, + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#f59e0b", + "index": "a1", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "tei_struct_text", + "x": 94.70040893554688, + "y": 230.17578125, + "width": 360.57574462890625, + "height": 40, + "text": "Transparency Exchange Identifier (TEI)\nurn:tei:::", + "fontSize": 16, + "fontFamily": 1, + "containerId": "tei_struct", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 150, + "versionNonce": 73082152, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 744852632, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601605550, + "link": null, + "locked": false, + "baseline": 36, + "index": "a2", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "Transparency Exchange Identifier (TEI)\nurn:tei:::", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 376, + "versionNonce": 896843096, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 819988572, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601611430, + "link": null, + "locked": false, + "type": "text", + "id": "t_types", + "x": 62.16796875, + "y": 139.89453125, + "text": "Supported Types: PURL, SWID, HASH,\nUUID, EAN/UPC, GTIN, ASIN, UDI", + "fontSize": 22.564062499999995, + "strokeColor": "#1e1e1e", + "width": 421.94796875000003, + "height": 56.41015625, + "baseline": 36, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "index": "a3", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": { + "type": 2 + }, + "containerId": null, + "originalText": "Supported Types: PURL, SWID, HASH,\nUUID, EAN/UPC, GTIN, ASIN, UDI", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 810552616, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 707153589, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "dns_res_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "rectangle", + "id": "dns_res", + "x": 650, + "y": 100, + "width": 200, + "height": 80, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#4a9eed", + "index": "a4", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "dns_res_text", + "x": 670.8, + "y": 120, + "width": 158.4, + "height": 40, + "text": "DNS Resolution\n(from domain-name)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "dns_res", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 1180744024, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 225575650, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 36, + "index": "a5", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "DNS Resolution\n(from domain-name)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 1478764584, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 793267654, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_tei_dns", + "x": 500, + "y": 140, + "width": 150, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 150, + 0 + ] + ], + "strokeColor": "#f59e0b", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "tei_struct", + "fixedPoint": [ + 1, + 0.5 + ], + "focus": 0 + }, + "endBinding": { + "elementId": "dns_res", + "fixedPoint": [ + 0, + 0.5 + ], + "focus": 0 + }, + "startArrowhead": null, + "lastCommittedPoint": null, + "index": "a6", + "fillStyle": "solid", + "backgroundColor": "transparent", + "roundness": { + "type": 2 + } + }, + { + "version": 3, + "versionNonce": 1236258392, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 277235211, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "well_known_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "rectangle", + "id": "well_known", + "x": 600, + "y": 300, + "width": 300, + "height": 100, + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#22c55e", + "index": "a7", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "well_known_text", + "x": 609.2, + "y": 330, + "width": 281.6, + "height": 40, + "text": "Discovery Endpoint\nhttps:///.well-known/tea", + "fontSize": 16, + "fontFamily": 1, + "containerId": "well_known", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 1153229608, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 130605028, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 36, + "index": "a8", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "Discovery Endpoint\nhttps:///.well-known/tea", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 475284312, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 966250622, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_dns_wk_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_dns_wk", + "x": 750, + "y": 180, + "width": 0, + "height": 120, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 120 + ] + ], + "strokeColor": "#4a9eed", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "dns_res", + "fixedPoint": [ + 0.5, + 1 + ], + "focus": 0 + }, + "endBinding": { + "elementId": "well_known", + "fixedPoint": [ + 0.5, + 0 + ], + "focus": 0 + }, + "startArrowhead": null, + "lastCommittedPoint": null, + "index": "a9", + "fillStyle": "solid", + "backgroundColor": "transparent", + "roundness": { + "type": 2 + } + }, + { + "type": "text", + "id": "a_dns_wk_text", + "x": 715.35, + "y": 231.25, + "width": 69.30000000000001, + "height": 17.5, + "text": "HTTPS GET", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_dns_wk", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 881212968, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 840751313, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 13.5, + "index": "aA", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "HTTPS GET", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 1822562392, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 916185838, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "json_resp_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "rectangle", + "id": "json_resp", + "x": 600, + "y": 500, + "width": 300, + "height": 100, + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#8b5cf6", + "index": "aB", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "json_resp_text", + "x": 626.8, + "y": 530, + "width": 246.40000000000003, + "height": 40, + "text": "TEA Server Index (JSON)\nList of endpoints & versions", + "fontSize": 16, + "fontFamily": 1, + "containerId": "json_resp", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 946616616, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 780073158, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 36, + "index": "aC", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "TEA Server Index (JSON)\nList of endpoints & versions", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 2121092440, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 223506811, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_wk_json_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_wk_json", + "x": 750, + "y": 400, + "width": 0, + "height": 100, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 100 + ] + ], + "strokeColor": "#22c55e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "well_known", + "fixedPoint": [ + 0.5, + 1 + ], + "focus": 0 + }, + "endBinding": { + "elementId": "json_resp", + "fixedPoint": [ + 0.5, + 0 + ], + "focus": 0 + }, + "startArrowhead": null, + "lastCommittedPoint": null, + "index": "aD", + "fillStyle": "solid", + "backgroundColor": "transparent", + "roundness": { + "type": 2 + } + }, + { + "type": "text", + "id": "a_wk_json_text", + "x": 669.15, + "y": 441.25, + "width": 161.70000000000002, + "height": 17.5, + "text": "Returns endpoint URLs", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_wk_json", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 1675285544, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 914463783, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 13.5, + "index": "aE", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "Returns endpoint URLs", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 2064245336, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 70566273, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "client_logic_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "rectangle", + "id": "client_logic", + "x": 100, + "y": 500, + "width": 300, + "height": 100, + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#ef4444", + "index": "aF", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "client_logic_text", + "x": 104.79999999999998, + "y": 520, + "width": 290.40000000000003, + "height": 60, + "text": "Client Selection Logic\nMatches version, honors priority,\nretries on failure (5xx, cert)", + "fontSize": 16, + "fontFamily": 1, + "containerId": "client_logic", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 2092830504, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 121030079, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 56, + "index": "aG", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "Client Selection Logic\nMatches version, honors priority,\nretries on failure (5xx, cert)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 1095489368, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 827435008, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_json_cli", + "x": 600, + "y": 550, + "width": 200, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -200, + 0 + ] + ], + "strokeColor": "#8b5cf6", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "json_resp", + "fixedPoint": [ + 0, + 0.5 + ], + "focus": 0 + }, + "endBinding": { + "elementId": "client_logic", + "fixedPoint": [ + 1, + 0.5 + ], + "focus": 0 + }, + "startArrowhead": null, + "lastCommittedPoint": null, + "index": "aH", + "fillStyle": "solid", + "backgroundColor": "transparent", + "roundness": { + "type": 2 + } + }, + { + "version": 3, + "versionNonce": 729934376, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 612330223, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "full_api_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "rectangle", + "id": "full_api", + "x": 100, + "y": 700, + "width": 350, + "height": 80, + "backgroundColor": "#eebefa", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#ec4899", + "index": "aI", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "full_api_text", + "x": 90.19999999999999, + "y": 722.5, + "width": 369.6, + "height": 35, + "text": "Constructed API Call\n/v/discovery?tei=", + "fontSize": 14, + "fontFamily": 1, + "containerId": "full_api", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 898051160, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 897653891, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 31, + "index": "aJ", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "Constructed API Call\n/v/discovery?tei=", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 722625832, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 863461482, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_cli_api", + "x": 250, + "y": 600, + "width": 0, + "height": 100, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 100 + ] + ], + "strokeColor": "#ef4444", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "client_logic", + "fixedPoint": [ + 0.5, + 1 + ], + "focus": 0 + }, + "endBinding": { + "elementId": "full_api", + "fixedPoint": [ + 0.5, + 0 + ], + "focus": 0 + }, + "startArrowhead": null, + "lastCommittedPoint": null, + "index": "aK", + "fillStyle": "solid", + "backgroundColor": "transparent", + "roundness": { + "type": 2 + } + }, + { + "version": 3, + "versionNonce": 294642008, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 935183848, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "auth_layer_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "rectangle", + "id": "auth_layer", + "x": 600, + "y": 700, + "width": 250, + "height": 100, + "backgroundColor": "#c3fae8", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#15803d", + "index": "aL", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "auth_layer_text", + "x": 601.8, + "y": 723.75, + "width": 246.40000000000003, + "height": 52.5, + "text": "Security / Auth Layer\n- HTTP Bearer Token\n- Mutual TLS (mTLS) Client Certs", + "fontSize": 14, + "fontFamily": 1, + "containerId": "auth_layer", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 2027651112, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 672485143, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 48.5, + "index": "aM", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "Security / Auth Layer\n- HTTP Bearer Token\n- Mutual TLS (mTLS) Client Certs", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "version": 3, + "versionNonce": 884602456, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 240663246, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "a_api_auth_text", + "type": "text" + } + ], + "updated": 1771601377884, + "link": null, + "locked": false, + "type": "arrow", + "id": "a_api_auth", + "x": 450, + "y": 740, + "width": 150, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 150, + 0 + ] + ], + "strokeColor": "#ec4899", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "full_api", + "fixedPoint": [ + 1, + 0.5 + ], + "focus": 0 + }, + "endBinding": { + "elementId": "auth_layer", + "fixedPoint": [ + 0, + 0.5 + ], + "focus": 0 + }, + "startArrowhead": null, + "lastCommittedPoint": null, + "index": "aN", + "fillStyle": "solid", + "backgroundColor": "transparent", + "roundness": { + "type": 2 + } + }, + { + "type": "text", + "id": "a_api_auth_text", + "x": 482.65, + "y": 731.25, + "width": 84.7, + "height": 17.5, + "text": "secured via", + "fontSize": 14, + "fontFamily": 1, + "containerId": "a_api_auth", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 3, + "versionNonce": 80296744, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 317220990, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771601377884, + "link": null, + "locked": false, + "baseline": 13.5, + "index": "aO", + "fillStyle": "solid", + "strokeWidth": 2, + "backgroundColor": "transparent", + "roundness": null, + "originalText": "secured via", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/doc/tea_publisher_flow.excalidraw b/doc/tea_publisher_flow.excalidraw new file mode 100644 index 0000000..b34cc6d --- /dev/null +++ b/doc/tea_publisher_flow.excalidraw @@ -0,0 +1,948 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "version": 2, + "versionNonce": 735189831, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 493599073, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "text", + "id": "t_seq2", + "x": 350, + "y": 1650, + "text": "TEA Publisher Flow", + "fontSize": 28, + "strokeColor": "#1e1e1e", + "width": 277.20000000000005, + "height": 35, + "baseline": 31, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "version": 2, + "versionNonce": 983350661, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 372839194, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pbHead_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "pbHead", + "x": 50, + "y": 1750, + "width": 120, + "height": 40, + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#ef4444", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "pbHead_text", + "x": 83.6, + "y": 1760, + "width": 52.800000000000004, + "height": 20, + "text": "Vendor", + "fontSize": 16, + "fontFamily": 1, + "containerId": "pbHead", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 891424686, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 519508987, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 16 + }, + { + "version": 2, + "versionNonce": 200057426, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 160900905, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pbLine", + "x": 110, + "y": 1790, + "width": 0, + "height": 350, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 350 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 890327808, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 961803583, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "ellipse", + "id": "pbh", + "x": 100, + "y": 1700, + "width": 20, + "height": 20, + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeColor": "#ef4444", + "strokeWidth": 2 + }, + { + "version": 2, + "versionNonce": 417291581, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 223982292, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "pbb", + "x": 100, + "y": 1722, + "width": 20, + "height": 20, + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#ef4444", + "strokeWidth": 2 + }, + { + "version": 2, + "versionNonce": 171505583, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 206442783, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "ppHead_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "ppHead", + "x": 300, + "y": 1750, + "width": 160, + "height": 40, + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#4a9eed", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "ppHead_text", + "x": 337.65, + "y": 1761.25, + "width": 84.7, + "height": 17.5, + "text": "TEA Product", + "fontSize": 14, + "fontFamily": 1, + "containerId": "ppHead", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 111956121, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 540702299, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 608506924, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 995563976, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "ppLine", + "x": 380, + "y": 1790, + "width": 0, + "height": 350, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 350 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 359271078, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 459983326, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pcHead_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "pcHead", + "x": 550, + "y": 1750, + "width": 160, + "height": 40, + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#8b5cf6", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "pcHead_text", + "x": 579.95, + "y": 1761.25, + "width": 100.10000000000001, + "height": 17.5, + "text": "TEA Component", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pcHead", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 339317497, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 266302514, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 43997061, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 66353842, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pcLine", + "x": 630, + "y": 1790, + "width": 0, + "height": 350, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 350 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 10162484, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 467471766, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pcHead2_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "rectangle", + "id": "pcHead2", + "x": 800, + "y": 1750, + "width": 180, + "height": 40, + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "roundness": { + "type": 3 + }, + "strokeColor": "#22c55e", + "strokeWidth": 2 + }, + { + "type": "text", + "id": "pcHead2_text", + "x": 836.1, + "y": 1761.25, + "width": 107.80000000000001, + "height": 17.5, + "text": "TEA Collection", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pcHead2", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 34816800, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 367252007, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 746602882, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 803867599, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pcLine2", + "x": 890, + "y": 1790, + "width": 0, + "height": 350, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 350 + ] + ], + "strokeColor": "#b0b0b0", + "strokeWidth": 1, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "version": 2, + "versionNonce": 479137662, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 8343542, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pseq1_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pseq1", + "x": 110, + "y": 1840, + "width": 270, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 270, + 0 + ] + ], + "strokeColor": "#4a9eed", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "pseq1_text", + "x": 171.85, + "y": 1831.25, + "width": 146.3, + "height": 17.5, + "text": "1. POST /v1/product", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pseq1", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 919186660, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 985070238, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 285675331, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 878566305, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pseq2_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pseq2", + "x": 380, + "y": 1880, + "width": -270, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -270, + 0 + ] + ], + "strokeColor": "#4a9eed", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "pseq2_text", + "x": 114.1, + "y": 1871.25, + "width": 261.8, + "height": 17.5, + "text": "2. Returns Product Identifier (PI)", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pseq2", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 365632409, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 430366614, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 500453078, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 484489509, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pseq3_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pseq3", + "x": 110, + "y": 1930, + "width": 520, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 520, + 0 + ] + ], + "strokeColor": "#8b5cf6", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "pseq3_text", + "x": 177.49999999999997, + "y": 1921.25, + "width": 385.00000000000006, + "height": 17.5, + "text": "3. POST /v1/component (with PI, Component Version)", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pseq3", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 996674539, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 149908619, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 732777045, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 375918744, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pseq4_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pseq4", + "x": 630, + "y": 1970, + "width": -520, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -520, + 0 + ] + ], + "strokeColor": "#8b5cf6", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "pseq4_text", + "x": 281.45, + "y": 1961.25, + "width": 177.10000000000002, + "height": 17.5, + "text": "4. Returns Component ID", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pseq4", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 290342518, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 985361675, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 86711719, + "isDeleted": false, + "strokeStyle": "solid", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 585995016, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pseq5_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pseq5", + "x": 110, + "y": 2020, + "width": 780, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + 780, + 0 + ] + ], + "strokeColor": "#22c55e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "pseq5_text", + "x": 288.25, + "y": 2011.25, + "width": 423.50000000000006, + "height": 17.5, + "text": "5. POST /v1/collection (with Component ID and Artifact)", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pseq5", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 322237589, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 645475552, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + }, + { + "version": 2, + "versionNonce": 718048721, + "isDeleted": false, + "strokeStyle": "dashed", + "strokeSharpness": "round", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 935023425, + "groupIds": [], + "frameId": null, + "boundElements": [ + { + "id": "pseq6_text", + "type": "text" + } + ], + "updated": 1771600797571, + "link": null, + "locked": false, + "type": "arrow", + "id": "pseq6", + "x": 890, + "y": 2060, + "width": -780, + "height": 0, + "points": [ + [ + 0, + 0 + ], + [ + -780, + 0 + ] + ], + "strokeColor": "#22c55e", + "strokeWidth": 2, + "endArrowhead": "arrow", + "startArrowhead": null + }, + { + "type": "text", + "id": "pseq6_text", + "x": 407.6, + "y": 2051.25, + "width": 184.8, + "height": 17.5, + "text": "6. Returns Collection ID", + "fontSize": 14, + "fontFamily": 1, + "containerId": "pseq6", + "verticalAlign": "middle", + "textAlign": "center", + "strokeColor": "#1e1e1e", + "version": 2, + "versionNonce": 288204797, + "isDeleted": false, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 464034892, + "groupIds": [], + "frameId": null, + "boundElements": [], + "updated": 1771600797571, + "link": null, + "locked": false, + "baseline": 13.5 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "currentItemStrokeColor": "#1e1e1e", + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemStrokeStyle": "solid", + "currentItemRoughness": 1, + "currentItemOpacity": 100, + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemTextAlign": "left", + "currentItemStrokeSharpness": "round" + } +} \ No newline at end of file diff --git a/doc/versioning.md b/doc/versioning.md new file mode 100644 index 0000000..f91330c --- /dev/null +++ b/doc/versioning.md @@ -0,0 +1,146 @@ +# Versioning + +The Transparency Exchange API (TEA) uses semantic versioning for API evolution. This document describes the versioning strategy, compatibility guarantees, and migration guidelines. + +## Semantic Versioning + +TEA follows [Semantic Versioning 2.0.0](https://semver.org/) for API versions: + +``` +MAJOR.MINOR.PATCH +``` + +### Version Components + +- **MAJOR**: Breaking changes that require client updates +- **MINOR**: Backward-compatible additions (new endpoints, optional fields) +- **PATCH**: Backward-compatible bug fixes + +### Pre-release Versions + +Pre-release versions use the format: + +``` +MAJOR.MINOR.PATCH-PRERELEASE +``` + +Examples: + +- `1.0.0-alpha.1` +- `1.0.0-beta.2` +- `1.0.0-rc.1` + +## API Versioning Strategy + +### URL Versioning + +API versions are included in the URL path: + +``` +/v{MAJOR}/... +``` + +Current version: `v1` + +### Content Negotiation + +For content that may evolve independently of the API version, use content negotiation with the `Accept` header: + +``` +Accept: application/vnd.cyclonedx+json; version=1.5 +``` + +## Compatibility Guarantees + +### Backward Compatibility + +- **PATCH** versions: Fully backward compatible +- **MINOR** versions: Backward compatible additions only +- **MAJOR** versions: May include breaking changes + +### Forward Compatibility + +Clients SHOULD ignore unknown fields in responses. Servers MUST NOT require unknown fields in requests. + +### Deprecation Policy + +1. Features are marked as deprecated in MINOR releases +2. Deprecated features are removed in the next MAJOR release +3. Deprecation notices include: + - Deprecation version + - Removal version + - Migration guidance + +## Version Discovery + +### Well-Known Endpoint + +Clients discover available API versions through `/.well-known/tea`: + +```json +{ + "schemaVersion": 1, + "endpoints": [ + { + "url": "https://api.example.com/tea/v1", + "versions": ["1.0.0", "1.1.0"], + "priority": 1 + } + ] +} +``` + +### Version Headers + +Servers MAY include version information in responses: + +``` +X-API-Version: 1.0.0 +``` + +## Migration Guidelines + +### Minor Version Upgrades + +1. Review release notes for new features +2. Update client code to handle new optional fields +3. Test with new version in staging environment +4. Gradually roll out updated clients + +### Major Version Upgrades + +1. Review breaking changes documentation +2. Update client code for required changes +3. Implement feature flags if needed +4. Test extensively in staging +5. Plan rollback strategy +6. Execute blue-green deployment + +### Testing Strategy + +- Maintain test suites for multiple API versions during transition periods +- Use contract testing to validate compatibility +- Implement canary deployments for gradual rollout + +## Implementation Considerations + +### Server-Side + +- Support multiple concurrent API versions +- Use version-aware routing +- Implement graceful degradation for older clients +- Provide version-specific documentation + +### Client-Side + +- Implement version negotiation logic +- Handle version-specific response formats +- Provide upgrade prompts for deprecated versions +- Support fallback to older versions when possible + +## Version Support Policy + +- Current MAJOR version receives active development and support +- Previous MAJOR version receives security updates only +- Versions older than N-1 MAJOR releases are deprecated +- Deprecation notices provided 6 months before end of support diff --git a/graph-schema.json b/graph-schema.json new file mode 100644 index 0000000..ac32c88 --- /dev/null +++ b/graph-schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Dependency Graph Schema", + "description": "Minimal JSON schema for dependency graph with nodes and edges.", + "type": "object", + "properties": { + "nodes": { + "type": "array", + "description": "List of dependency nodes.", + "items": { + "type": "object", + "properties": { + "purl": { + "type": "string", + "description": "Package URL (PURL) identifier." + }, + "name": { + "type": "string", + "description": "Package name." + }, + "version": { + "type": "string", + "description": "Exact version." + }, + "digest": { + "type": "string", + "description": "Content digest (e.g., SHA256)." + }, + "license": { + "type": "string", + "description": "License SPDX identifier." + }, + "source_repo": { + "type": "string", + "description": "Source repository URL." + }, + "lifecycle_state": { + "enum": ["ACTIVE", "DEPRECATED", "QUARANTINED", "RETIRED"], + "description": "Lifecycle state for deprecation protocol." + } + }, + "required": ["purl", "name", "version", "digest", "lifecycle_state"] + } + }, + "edges": { + "type": "array", + "description": "List of dependency edges.", + "items": { + "type": "object", + "properties": { + "from_purl": { + "type": "string", + "description": "PURL of the dependent package." + }, + "to_purl": { + "type": "string", + "description": "PURL of the dependency." + }, + "scope": { + "enum": ["build", "runtime"], + "description": "Scope of the dependency." + }, + "reason": { + "enum": ["direct", "transitive"], + "description": "Direct or transitive dependency." + } + }, + "required": ["from_purl", "to_purl", "scope", "reason"] + } + } + }, + "required": ["nodes", "edges"] +} diff --git a/proto/.gitignore b/proto/.gitignore new file mode 100644 index 0000000..61c84f5 --- /dev/null +++ b/proto/.gitignore @@ -0,0 +1,16 @@ +# Generated code +gen/ + +# Buf dependency cache +.buf-deps/ + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/proto/Makefile b/proto/Makefile new file mode 100644 index 0000000..cea9c8f --- /dev/null +++ b/proto/Makefile @@ -0,0 +1,114 @@ +# TEA Protobuf Makefile +# Common commands for working with TEA protobuf definitions + +.PHONY: all deps lint breaking generate generate-go generate-rust export-deps validate verify clean help + +# Default target +all: deps validate lint generate generate-rust + +# Install dependencies +deps: + @echo "==> Updating Buf dependencies..." + buf dep update + +# Lint protobuf files +lint: + @echo "==> Linting protobuf files..." + buf lint + +# Check for breaking changes against main branch +breaking: + @echo "==> Checking for breaking changes..." + buf breaking --against '.git#branch=main' + +# Check for breaking changes against published BSR module +breaking-bsr: + @echo "==> Checking for breaking changes against BSR..." + buf breaking --against buf.build/cyclonedx/tea + +# Generate Buf-managed code +generate: + @echo "==> Generating code..." + buf generate + @echo "==> Buf-managed code generated in gen/go/" + +# Generate only Go code +generate-go: + @echo "==> Generating Go code..." + buf generate --template buf.gen.yaml --include-imports \ + --path tea/v1/common.proto \ + --path tea/v1/product.proto \ + --path tea/v1/component.proto \ + --path tea/v1/collection.proto \ + --path tea/v1/artifact.proto \ + --path tea/v1/discovery.proto \ + --path tea/v1/consumer.proto \ + --path tea/v1/publisher.proto \ + --path tea/v1/insights.proto + +# Generate Rust bindings via tea-server's tonic-build pipeline +generate-rust: export-deps + @echo "==> Generating Rust bindings..." + cargo check --manifest-path ../tea-server/Cargo.toml --locked + @echo "==> Rust bindings generated via tea-server/build.rs" + +# Format protobuf files +format: + @echo "==> Formatting protobuf files..." + buf format -w + +# Export dependencies (for IDE support) +export-deps: + @echo "==> Exporting dependencies..." + rm -rf .buf-deps + buf export . --output .buf-deps + @echo "==> Dependencies exported to .buf-deps/" + +# Validate Buf workspace configuration +validate: + @echo "==> Validating buf workspace..." + buf config ls-modules >/dev/null + @echo "==> Buf workspace configuration is valid" + +# Build (lint + breaking check) +build: validate lint breaking + @echo "==> Build checks passed" + +# Full local verification for schema + generated artifacts +verify: validate lint generate generate-rust + @echo "==> Proto verification passed" + +# Push to Buf Schema Registry +push: + @echo "==> Pushing to Buf Schema Registry..." + buf push + +# Clean generated files +clean: + @echo "==> Cleaning generated files..." + rm -rf gen/ + rm -rf .buf-deps/ + +# Show help +help: + @echo "TEA Protobuf Makefile" + @echo "" + @echo "Usage: make [target]" + @echo "" + @echo "Targets:" + @echo " all - deps + validate + lint + generate + generate-rust (default)" + @echo " deps - Update Buf dependencies" + @echo " validate - Validate Buf workspace configuration" + @echo " lint - Lint protobuf files" + @echo " breaking - Check for breaking changes (vs main branch)" + @echo " breaking-bsr - Check for breaking changes (vs BSR)" + @echo " generate - Generate Buf-managed code (Go)" + @echo " generate-go - Generate only Go code" + @echo " generate-rust - Generate Rust bindings via tea-server/build.rs" + @echo " format - Format protobuf files" + @echo " export-deps - Export dependencies for IDE support" + @echo " verify - Validate + lint + generate + generate-rust" + @echo " build - Run lint + breaking checks" + @echo " push - Push to Buf Schema Registry" + @echo " clean - Remove generated files" + @echo " help - Show this help message" diff --git a/proto/buf.gen.yaml b/proto/buf.gen.yaml new file mode 100644 index 0000000..4558008 --- /dev/null +++ b/proto/buf.gen.yaml @@ -0,0 +1,30 @@ +# Buf V2 code generation configuration for TEA +# https://buf.build/docs/configuration/v2/buf-gen-yaml +version: v2 +clean: true + +# Code generation plugins +plugins: + # Go protobuf messages and gRPC stubs + - remote: buf.build/protocolbuffers/go:v1.36.5 + out: gen/go + opt: + - paths=source_relative + + # Go gRPC stubs + - remote: buf.build/grpc/go:v1.6.0 + out: gen/go + opt: + - paths=source_relative + - require_unimplemented_servers=false + +managed: + enabled: true + disable: + - file_option: go_package + module: buf.build/bufbuild/protovalidate +inputs: + # Generate from local proto files + - directory: . + exclude_paths: + - .buf-deps diff --git a/proto/buf.lock b/proto/buf.lock new file mode 100644 index 0000000..0a2ba2e --- /dev/null +++ b/proto/buf.lock @@ -0,0 +1,12 @@ +# Generated by buf. DO NOT EDIT. +version: v2 +deps: + - name: buf.build/bufbuild/protovalidate + commit: 2a1774d888024a9b93ce7eb4b59f6a83 + digest: b5:6b7f9bc919b65e5b79d7b726ffc03d6f815a412d6b792970fa6f065cae162107bd0a9d47272c8ab1a2c9514e87b13d3fbf71df614374d62d2183afb64be2d30a + - name: buf.build/googleapis/googleapis + commit: 004180b77378443887d3b55cabc00384 + digest: b5:e8f475fe3330f31f5fd86ac689093bcd274e19611a09db91f41d637cb9197881ce89882b94d13a58738e53c91c6e4bae7dc1feba85f590164c975a89e25115dc + - name: buf.build/grpc-ecosystem/grpc-gateway + commit: 6467306b4f624747aaf6266762ee7a1c + digest: b5:c2caa61467d992749812c909f93c07e9a667da33c758a7c1973d63136c23b3cafcc079985b12cdf54a10049ed3297418f1eda42cdffdcf34113792dcc3a990af diff --git a/proto/buf.yaml b/proto/buf.yaml new file mode 100644 index 0000000..9f27686 --- /dev/null +++ b/proto/buf.yaml @@ -0,0 +1,46 @@ +# Buf V2 module configuration for TEA (Transparency Exchange API) +# https://buf.build/docs/configuration/v2/buf-yaml +version: v2 + +modules: + - path: . + name: buf.build/cyclonedx/tea + excludes: + - .buf-deps + +# Dependencies from Buf Schema Registry +deps: + - buf.build/bufbuild/protovalidate + # Google API annotations for gRPC-Gateway HTTP bindings + - buf.build/googleapis/googleapis + # gRPC-Gateway annotations + - buf.build/grpc-ecosystem/grpc-gateway +# Linting configuration +lint: + use: + - STANDARD + - COMMENTS + except: + # Allow v1 instead of requiring v1beta1 for initial release + - PACKAGE_VERSION_SUFFIX + # TEA intentionally returns canonical resource messages directly for many + # read/write RPCs, so the standard request/response wrapper naming rules + # are too restrictive for this schema. + - RPC_REQUEST_RESPONSE_UNIQUE + - RPC_REQUEST_STANDARD_NAME + - RPC_RESPONSE_STANDARD_NAME + disallow_comment_ignores: true + enum_zero_value_suffix: _UNSPECIFIED + rpc_allow_same_request_response: false + rpc_allow_google_protobuf_empty_requests: true + rpc_allow_google_protobuf_empty_responses: true + service_suffix: Service + +# Breaking change detection +breaking: + use: + - FILE + except: + # Allow during beta development + - FIELD_SAME_DEFAULT + ignore_unstable_packages: true 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/insights.proto b/proto/tea/v1/insights.proto new file mode 100644 index 0000000..160f387 --- /dev/null +++ b/proto/tea/v1/insights.proto @@ -0,0 +1,791 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.proto"; +import "tea/v1/artifact.proto"; + +option go_package = "github.com/CycloneDX/transparency-exchange-api/gen/go/tea/v1;teav1"; +option java_package = "org.cyclonedx.tea.v1"; +option java_multiple_files = true; +option csharp_namespace = "CycloneDX.Tea.V1"; + +// ============================================================================ +// Insights Service +// ============================================================================ + +// InsightsService provides query and analytics capabilities for TEA data. +// This enables "limited transparency" queries without requiring full artifact +// downloads and processing by the consumer. +// +// The service supports: +// - CEL (Common Expression Language) queries +// - Pre-built queries for common use cases +// - Vulnerability analysis +// - Component dependency analysis +// +// This service is an optional part of the TEA specification. +// Authentication is typically required for insights queries. +service InsightsService { + // ========================================================================== + // CEL Query Operations + // ========================================================================== + + // Query executes a CEL expression against TEA data. + // CEL provides a type-safe, sandboxed query language. + // See: https://github.com/google/cel-spec + rpc Query(QueryRequest) returns (QueryResponse) { + option (google.api.http) = { + post: "/v1/insights/query" + body: "*" + }; + } + + // ValidateQuery validates a CEL expression without executing it. + rpc ValidateQuery(ValidateQueryRequest) returns (ValidateQueryResponse) { + option (google.api.http) = { + post: "/v1/insights/query/validate" + body: "*" + }; + } + + // ========================================================================== + // Pre-built Queries + // ========================================================================== + + // GetVulnerabilitySummary returns vulnerability information for a release. + rpc GetVulnerabilitySummary(GetVulnerabilitySummaryRequest) returns (VulnerabilitySummary) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/vulnerabilities" + }; + } + + // GetComponentDependencies returns component dependencies for a release. + rpc GetComponentDependencies(GetComponentDependenciesRequest) returns (ComponentDependencies) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/dependencies" + }; + } + + // FindAffectedReleases finds releases affected by a specific component. + rpc FindAffectedReleases(FindAffectedReleasesRequest) returns (FindAffectedReleasesResponse) { + option (google.api.http) = { + get: "/v1/insights/affected" + }; + } + + // GetLicenseSummary returns license information for a release. + rpc GetLicenseSummary(GetLicenseSummaryRequest) returns (LicenseSummary) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/licenses" + }; + } + + // GetCryptoInventory returns cryptographic assets for a release. + // Based on CBOM (Cryptographic Bill of Materials). + rpc GetCryptoInventory(GetCryptoInventoryRequest) returns (CryptoInventory) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/crypto" + }; + } + + // ========================================================================== + // Analytics + // ========================================================================== + + // GetReleaseMetrics returns analytics for a release. + rpc GetReleaseMetrics(GetReleaseMetricsRequest) returns (ReleaseMetrics) { + option (google.api.http) = { + get: "/v1/insights/releases/{release_uuid}/metrics" + }; + } + + // CompareSBOMs compares two SBOMs for drift detection. + rpc CompareSBOMs(CompareSBOMsRequest) returns (SBOMComparison) { + option (google.api.http) = { + post: "/v1/insights/compare/sbom" + body: "*" + }; + } +} + +// ============================================================================ +// CEL Query Messages +// ============================================================================ + +// QueryRequest contains a CEL query to execute. +message QueryRequest { + // CEL expression to evaluate. + // The expression has access to TEA data objects (products, components, + // releases, artifacts, sboms, vex, etc.). + // + // Example expressions: + // - components.exists(c, c.purl == "pkg:maven/org.apache.logging.log4j/log4j-core") + // - sbom.components.filter(c, c.vulnerabilities.size() > 0) + // - releases.filter(r, r.artifacts.exists(a, a.type == "VULNERABILITIES")) + string expression = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 10000, + (buf.validate.field).required = true + ]; + + // Scope of the query (what data to query against). + QueryScope scope = 2; + + // Variables to bind in the expression. + map variables = 3; + + // Maximum execution time (server may enforce limits). + int32 timeout_seconds = 4 [json_name = "timeoutSeconds"]; + + // Pagination for results. + PageRequest pagination = 5; + + // Output format. + QueryOutputFormat output_format = 6 [json_name = "outputFormat"]; +} + +// QueryScope defines what data the query operates on. +message QueryScope { + // Query against a specific release. + optional string release_uuid = 1 [json_name = "releaseUuid"]; + + // Query against a specific product. + optional string product_uuid = 2 [json_name = "productUuid"]; + + // Query against all data (requires special permission). + bool global = 3; + + // Artifact types to include in scope. + repeated ArtifactType artifact_types = 4 [json_name = "artifactTypes"]; + + // Date range for temporal queries. + DateRange date_range = 5 [json_name = "dateRange"]; +} + +// QueryOutputFormat specifies how to format query results. +enum QueryOutputFormat { + // Unspecified - server default (JSON). + QUERY_OUTPUT_FORMAT_UNSPECIFIED = 0; + + // JSON output. + QUERY_OUTPUT_FORMAT_JSON = 1; + + // Protocol Buffer Any. + QUERY_OUTPUT_FORMAT_PROTO = 2; + + // CSV (for tabular results). + QUERY_OUTPUT_FORMAT_CSV = 3; +} + +// QueryResponse contains the results of a CEL query. +message QueryResponse { + // Query result as JSON (when output_format is JSON). + google.protobuf.Struct result = 1; + + // Query result as Any (when output_format is PROTO). + google.protobuf.Any result_proto = 2 [json_name = "resultProto"]; + + // Query result as CSV (when output_format is CSV). + string result_csv = 3 [json_name = "resultCsv"]; + + // Number of results. + int64 result_count = 4 [json_name = "resultCount"]; + + // Execution time in milliseconds. + int64 execution_time_ms = 5 [json_name = "executionTimeMs"]; + + // Pagination for continued results. + PageResponse pagination = 6; + + // Warnings during execution. + repeated string warnings = 7; +} + +// ValidateQueryRequest validates a CEL expression. +message ValidateQueryRequest { + // CEL expression to validate. + string expression = 1 [(buf.validate.field).required = true]; +} + +// ValidateQueryResponse contains validation results. +message ValidateQueryResponse { + // Whether the expression is valid. + bool valid = 1; + + // Validation errors (if any). + repeated QueryValidationError errors = 2; + + // Inferred return type. + string return_type = 3 [json_name = "returnType"]; + + // Estimated complexity score. + int32 complexity_score = 4 [json_name = "complexityScore"]; +} + +// QueryValidationError describes a CEL validation error. +message QueryValidationError { + // Error message. + string message = 1; + + // Position in expression. + int32 position = 2; + + // Line number. + int32 line = 3; + + // Column number. + int32 column = 4; +} + +// ============================================================================ +// Vulnerability Messages +// ============================================================================ + +// GetVulnerabilitySummaryRequest requests vulnerability summary. +message GetVulnerabilitySummaryRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; + + // Include VEX analysis (not just raw vulnerabilities). + bool include_vex_analysis = 2 [json_name = "includeVexAnalysis"]; + + // Minimum severity to include. + VulnerabilitySeverity min_severity = 3 [json_name = "minSeverity"]; +} + +// VulnerabilitySummary contains vulnerability information. +message VulnerabilitySummary { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Total vulnerabilities found. + int32 total_count = 2 [json_name = "totalCount"]; + + // Counts by severity. + VulnerabilityCounts by_severity = 3 [json_name = "bySeverity"]; + + // Counts by status (from VEX). + VexStatusCounts by_vex_status = 4 [json_name = "byVexStatus"]; + + // Top vulnerabilities (most severe). + repeated VulnerabilityInfo top_vulnerabilities = 5 [json_name = "topVulnerabilities"]; + + // When the analysis was performed. + google.protobuf.Timestamp analyzed_at = 6 [json_name = "analyzedAt"]; + + // Artifacts used for this analysis. + repeated string source_artifact_uuids = 7 [json_name = "sourceArtifactUuids"]; +} + +// VulnerabilitySeverity levels. +enum VulnerabilitySeverity { + // Severity could not be determined. + VULNERABILITY_SEVERITY_UNSPECIFIED = 0; + // No vulnerability is present. + VULNERABILITY_SEVERITY_NONE = 1; + // Vulnerability has low impact. + VULNERABILITY_SEVERITY_LOW = 2; + // Vulnerability has medium impact. + VULNERABILITY_SEVERITY_MEDIUM = 3; + // Vulnerability has high impact. + VULNERABILITY_SEVERITY_HIGH = 4; + // Vulnerability has critical impact. + VULNERABILITY_SEVERITY_CRITICAL = 5; +} + +// VulnerabilityCounts by severity. +message VulnerabilityCounts { + // Number of critical vulnerabilities. + int32 critical = 1; + // Number of high-severity vulnerabilities. + int32 high = 2; + // Number of medium-severity vulnerabilities. + int32 medium = 3; + // Number of low-severity vulnerabilities. + int32 low = 4; + // Number of explicitly non-applicable vulnerabilities. + int32 none = 5; + // Number of vulnerabilities with unknown severity. + int32 unknown = 6; +} + +// VexStatusCounts by VEX status. +message VexStatusCounts { + // Number of affected findings. + int32 affected = 1; + // Number of findings marked not affected. + int32 not_affected = 2; + // Number of findings marked fixed. + int32 fixed = 3; + // Number of findings still under investigation. + int32 under_investigation = 4 [json_name = "underInvestigation"]; +} + +// VulnerabilityInfo contains details about a vulnerability. +message VulnerabilityInfo { + // CVE or other vulnerability ID. + string id = 1; + + // Severity level. + VulnerabilitySeverity severity = 2; + + // CVSS score (if available). + optional float cvss_score = 3 [json_name = "cvssScore"]; + + // Brief description. + string description = 4; + + // Affected component. + Identifier affected_component = 5 [json_name = "affectedComponent"]; + + // VEX status (if VEX is available). + optional VexStatus vex_status = 6 [json_name = "vexStatus"]; + + // VEX justification. + string vex_justification = 7 [json_name = "vexJustification"]; +} + +// VexStatus from VEX documents. +enum VexStatus { + // No VEX status is available. + VEX_STATUS_UNSPECIFIED = 0; + // The product is affected by the vulnerability. + VEX_STATUS_AFFECTED = 1; + // The product is not affected by the vulnerability. + VEX_STATUS_NOT_AFFECTED = 2; + // The vulnerability has been fixed. + VEX_STATUS_FIXED = 3; + // Impact analysis is still in progress. + VEX_STATUS_UNDER_INVESTIGATION = 4; +} + +// ============================================================================ +// Dependency Messages +// ============================================================================ + +// GetComponentDependenciesRequest requests dependency tree. +message GetComponentDependenciesRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; + + // Maximum depth to traverse (0 = unlimited). + int32 max_depth = 2 [json_name = "maxDepth"]; + + // Include transitive dependencies. + bool include_transitive = 3 [json_name = "includeTransitive"]; +} + +// ComponentDependencies contains the dependency tree. +message ComponentDependencies { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Root component. + DependencyNode root = 2; + + // Total dependency count (including transitive). + int32 total_count = 3 [json_name = "totalCount"]; + + // Direct dependency count. + int32 direct_count = 4 [json_name = "directCount"]; + + // Transitive dependency count. + int32 transitive_count = 5 [json_name = "transitiveCount"]; +} + +// DependencyNode represents a component in the dependency tree. +message DependencyNode { + // Component identifier. + Identifier identifier = 1; + + // Component name. + string name = 2; + + // Component version. + string version = 3; + + // Dependency scope (runtime, dev, test, etc.). + string scope = 4; + + // Direct dependencies. + repeated DependencyNode dependencies = 5; + + // Depth in the tree. + int32 depth = 6; +} + +// FindAffectedReleasesRequest finds releases using a component. +message FindAffectedReleasesRequest { + // Identifier of the component to search for. + Identifier identifier = 1 [(buf.validate.field).required = true]; + + // Version range (VERS format). + string version_range = 2 [json_name = "versionRange"]; + + // Pagination. + PageRequest pagination = 3; +} + +// FindAffectedReleasesResponse contains matching releases. +message FindAffectedReleasesResponse { + // Releases that use the specified component. + repeated AffectedRelease releases = 1; + + // Pagination. + PageResponse pagination = 2; +} + +// AffectedRelease contains info about an affected release. +message AffectedRelease { + // Release UUID. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Release version. + string version = 2; + + // Product name (if available). + string product_name = 3 [json_name = "productName"]; + + // How the component is used (direct/transitive). + string dependency_type = 4 [json_name = "dependencyType"]; + + // Specific version of the component used. + string component_version = 5 [json_name = "componentVersion"]; +} + +// ============================================================================ +// License Messages +// ============================================================================ + +// GetLicenseSummaryRequest requests license summary. +message GetLicenseSummaryRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; +} + +// LicenseSummary contains license information. +message LicenseSummary { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Total components analyzed. + int32 total_components = 2 [json_name = "totalComponents"]; + + // Components with identified licenses. + int32 with_license = 3 [json_name = "withLicense"]; + + // Components without identified licenses. + int32 without_license = 4 [json_name = "withoutLicense"]; + + // License distribution. + repeated LicenseCount licenses = 5; + + // License conflicts (if any). + repeated LicenseConflict conflicts = 6; +} + +// LicenseCount represents a license and its usage count. +message LicenseCount { + // SPDX license ID. + string spdx_id = 1 [json_name = "spdxId"]; + + // License name. + string name = 2; + + // Number of components using this license. + int32 count = 3; + + // License category (permissive, copyleft, proprietary). + string category = 4; +} + +// LicenseConflict describes a potential license conflict. +message LicenseConflict { + // First license. + string license1 = 1; + + // Second license. + string license2 = 2; + + // Description of the conflict. + string description = 3; + + // Severity (info, warning, error). + string severity = 4; +} + +// ============================================================================ +// Crypto Messages +// ============================================================================ + +// GetCryptoInventoryRequest requests cryptographic inventory. +message GetCryptoInventoryRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; +} + +// CryptoInventory contains cryptographic assets. +message CryptoInventory { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Cryptographic algorithms in use. + repeated CryptoAlgorithm algorithms = 2; + + // Certificates found. + repeated CryptoCertificate certificates = 3; + + // Quantum readiness assessment. + QuantumReadiness quantum_readiness = 4 [json_name = "quantumReadiness"]; +} + +// CryptoAlgorithm describes a cryptographic algorithm. +message CryptoAlgorithm { + // Algorithm name (e.g., "AES-256-GCM", "RSA-2048"). + string name = 1; + + // Algorithm type (symmetric, asymmetric, hash, etc.). + string type = 2; + + // Usage (encryption, signing, hashing, key-exchange). + string usage = 3; + + // Key size (if applicable). + int32 key_size = 4 [json_name = "keySize"]; + + // Is quantum-safe. + bool quantum_safe = 5 [json_name = "quantumSafe"]; + + // Components using this algorithm. + repeated string components = 6; +} + +// CryptoCertificate describes a certificate. +message CryptoCertificate { + // Certificate subject. + string subject = 1; + + // Certificate issuer. + string issuer = 2; + + // Expiration date. + google.protobuf.Timestamp expires = 3; + + // Public key algorithm. + string algorithm = 4; + + // Key size. + int32 key_size = 5 [json_name = "keySize"]; +} + +// QuantumReadiness assesses post-quantum cryptography readiness. +message QuantumReadiness { + // Overall readiness score (0-100). + int32 score = 1; + + // Number of quantum-vulnerable algorithms. + int32 vulnerable_count = 2 [json_name = "vulnerableCount"]; + + // Number of quantum-safe algorithms. + int32 safe_count = 3 [json_name = "safeCount"]; + + // Recommendations. + repeated string recommendations = 4; +} + +// ============================================================================ +// Metrics Messages +// ============================================================================ + +// GetReleaseMetricsRequest requests release metrics. +message GetReleaseMetricsRequest { + // UUID of the release. + string release_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "releaseUuid" + ]; +} + +// ReleaseMetrics contains analytics for a release. +message ReleaseMetrics { + // UUID of the release. + string release_uuid = 1 [json_name = "releaseUuid"]; + + // Component metrics. + ComponentMetrics components = 2; + + // Artifact metrics. + ArtifactMetrics artifacts = 3; + + // Collection metrics. + CollectionMetrics collection = 4; +} + +// ComponentMetrics contains component-related metrics. +message ComponentMetrics { + // Total components. + int32 total = 1; + + // Direct dependencies. + int32 direct = 2; + + // Transitive dependencies. + int32 transitive = 3; + + // By type (library, framework, etc.). + map by_type = 4 [json_name = "byType"]; + + // By ecosystem (maven, npm, etc.). + map by_ecosystem = 5 [json_name = "byEcosystem"]; +} + +// ArtifactMetrics contains artifact-related metrics. +message ArtifactMetrics { + // Total artifacts. + int32 total = 1; + + // By type. + map by_type = 2 [json_name = "byType"]; + + // Total size in bytes. + int64 total_size_bytes = 3 [json_name = "totalSizeBytes"]; +} + +// CollectionMetrics contains collection-related metrics. +message CollectionMetrics { + // Collection version. + int32 version = 1; + + // Number of updates. + int32 update_count = 2 [json_name = "updateCount"]; + + // Last update timestamp. + google.protobuf.Timestamp last_updated = 3 [json_name = "lastUpdated"]; + + // Is signed. + bool is_signed = 4 [json_name = "isSigned"]; +} + +// ============================================================================ +// SBOM Comparison Messages +// ============================================================================ + +// CompareSBOMsRequest compares two SBOMs. +message CompareSBOMsRequest { + // First SBOM (base). + oneof base { + // Base release UUID. + string base_release_uuid = 1; + // Base artifact UUID. + string base_artifact_uuid = 2; + } + + // Second SBOM (target). + oneof target { + // Target release UUID. + string target_release_uuid = 3; + // Target artifact UUID. + string target_artifact_uuid = 4; + } + + // Include detailed component changes. + bool include_details = 5 [json_name = "includeDetails"]; +} + +// SBOMComparison contains comparison results. +message SBOMComparison { + // Components added in target. + repeated ComponentChange added = 1; + + // Components removed in target. + repeated ComponentChange removed = 2; + + // Components with version changes. + repeated ComponentVersionChange updated = 3; + + // Components unchanged. + int32 unchanged_count = 4 [json_name = "unchangedCount"]; + + // Summary statistics. + ComparisonSummary summary = 5; +} + +// ComponentChange describes an added or removed component. +message ComponentChange { + // Component identifier. + Identifier identifier = 1; + + // Component name. + string name = 2; + + // Component version. + string version = 3; +} + +// ComponentVersionChange describes a version change. +message ComponentVersionChange { + // Component identifier. + Identifier identifier = 1; + + // Component name. + string name = 2; + + // Previous version. + string old_version = 3 [json_name = "oldVersion"]; + + // New version. + string new_version = 4 [json_name = "newVersion"]; + + // Is upgrade (true) or downgrade (false). + bool is_upgrade = 5 [json_name = "isUpgrade"]; +} + +// ComparisonSummary contains comparison statistics. +message ComparisonSummary { + // Components in base SBOM. + int32 base_count = 1 [json_name = "baseCount"]; + + // Components in target SBOM. + int32 target_count = 2 [json_name = "targetCount"]; + + // Components added. + int32 added_count = 3 [json_name = "addedCount"]; + + // Components removed. + int32 removed_count = 4 [json_name = "removedCount"]; + + // Components updated. + int32 updated_count = 5 [json_name = "updatedCount"]; + + // Components unchanged. + int32 unchanged_count = 6 [json_name = "unchangedCount"]; + + // Similarity percentage. + float similarity_percent = 7 [json_name = "similarityPercent"]; +} diff --git a/proto/tea/v1/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/proto/tea/v1/publisher.proto b/proto/tea/v1/publisher.proto new file mode 100644 index 0000000..93ace40 --- /dev/null +++ b/proto/tea/v1/publisher.proto @@ -0,0 +1,792 @@ +// Copyright 2024-2026 CycloneDX Contributors +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package tea.v1; + +import "buf/validate/validate.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/timestamp.proto"; +import "tea/v1/common.proto"; +import "tea/v1/product.proto"; +import "tea/v1/component.proto"; +import "tea/v1/collection.proto"; +import "tea/v1/artifact.proto"; + +option go_package = "github.com/CycloneDX/transparency-exchange-api/gen/go/tea/v1;teav1"; +option java_package = "org.cyclonedx.tea.v1"; +option java_multiple_files = true; +option csharp_namespace = "CycloneDX.Tea.V1"; + +// ============================================================================ +// Publisher Service +// ============================================================================ + +// PublisherService provides write access for artifact publishers to manage +// transparency artifacts in the TEA server. +// +// All endpoints require authentication and appropriate authorization. +// The exact auth mechanism and scope vocabulary are deployment-specific, but +// implementations MUST reject unauthorized writes. +// +// This service implements the TEA Publisher API specification. +// Note: The Publisher API is a recommended (optional) part of the TEA spec. +// Implementations MAY expose only a subset of these RPCs, but unsupported +// operations MUST fail explicitly rather than partially succeeding. +service PublisherService { + // ========================================================================== + // Product Management + // ========================================================================== + + // CreateProduct creates a new product. + rpc CreateProduct(CreateProductRequest) returns (Product) { + option (google.api.http) = { + post: "/v1/publisher/products" + body: "*" + }; + } + + // UpdateProduct updates an existing product. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateProduct(UpdateProductRequest) returns (Product) { + option (google.api.http) = { + put: "/v1/publisher/products/{uuid}" + body: "*" + }; + } + + // DeleteProduct removes a product and optionally its releases. + // Implementations SHOULD fail with FAILED_PRECONDITION when cascade=false and + // dependent releases still exist. + rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse) { + option (google.api.http) = { + delete: "/v1/publisher/products/{uuid}" + }; + } + + // CreateProductRelease creates a new release for a product. + rpc CreateProductRelease(CreateProductReleaseRequest) returns (ProductRelease) { + option (google.api.http) = { + post: "/v1/publisher/product-releases" + body: "*" + }; + } + + // UpdateProductRelease updates an existing product release. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateProductRelease(UpdateProductReleaseRequest) returns (ProductRelease) { + option (google.api.http) = { + put: "/v1/publisher/product-releases/{uuid}" + body: "*" + }; + } + + // DeleteProductRelease removes a product release. + rpc DeleteProductRelease(DeleteProductReleaseRequest) returns (DeleteProductReleaseResponse) { + option (google.api.http) = { + delete: "/v1/publisher/product-releases/{uuid}" + }; + } + + // ========================================================================== + // Component Management + // ========================================================================== + + // CreateComponent creates a new component. + rpc CreateComponent(CreateComponentRequest) returns (Component) { + option (google.api.http) = { + post: "/v1/publisher/components" + body: "*" + }; + } + + // UpdateComponent updates an existing component. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateComponent(UpdateComponentRequest) returns (Component) { + option (google.api.http) = { + put: "/v1/publisher/components/{uuid}" + body: "*" + }; + } + + // DeleteComponent removes a component and optionally its releases. + // Implementations SHOULD fail with FAILED_PRECONDITION when cascade=false and + // dependent releases still exist. + rpc DeleteComponent(DeleteComponentRequest) returns (DeleteComponentResponse) { + option (google.api.http) = { + delete: "/v1/publisher/components/{uuid}" + }; + } + + // CreateComponentRelease creates a new release for a component. + rpc CreateComponentRelease(CreateComponentReleaseRequest) returns (ComponentRelease) { + option (google.api.http) = { + post: "/v1/publisher/component-releases" + body: "*" + }; + } + + // UpdateComponentRelease updates an existing component release. + // Note: pre_release can only be changed from true to false, not vice versa. + // Fields not listed in update_mask MUST be preserved as-is. + rpc UpdateComponentRelease(UpdateComponentReleaseRequest) returns (ComponentRelease) { + option (google.api.http) = { + put: "/v1/publisher/component-releases/{uuid}" + body: "*" + }; + } + + // DeleteComponentRelease removes a component release. + rpc DeleteComponentRelease(DeleteComponentReleaseRequest) returns (DeleteComponentReleaseResponse) { + option (google.api.http) = { + delete: "/v1/publisher/component-releases/{uuid}" + }; + } + + // ========================================================================== + // Artifact Management + // ========================================================================== + + // UploadArtifact uploads a new artifact. + // Artifacts are immutable once created. The first streamed frame MUST contain + // metadata; subsequent frames carry content bytes. + rpc UploadArtifact(stream UploadArtifactRequest) returns (Artifact) { + option (google.api.http) = { + post: "/v1/publisher/artifacts" + body: "*" + }; + } + + // CreateArtifactFromUrl creates an artifact from an external URL. + // The server fetches the content, verifies the declared checksums, and then + // registers the artifact metadata. Implementations MAY reject non-HTTPS URLs + // or private-network source URLs for safety. + rpc CreateArtifactFromUrl(CreateArtifactFromUrlRequest) returns (Artifact) { + option (google.api.http) = { + post: "/v1/publisher/artifacts/from-url" + body: "*" + }; + } + + // DeleteArtifact removes an artifact. + // Artifacts in use by collections MUST NOT be deleted unless the + // implementation can safely and explicitly handle force deletion. + rpc DeleteArtifact(DeleteArtifactRequest) returns (DeleteArtifactResponse) { + option (google.api.http) = { + delete: "/v1/publisher/artifacts/{uuid}" + }; + } + + // ========================================================================== + // Collection Management + // ========================================================================== + + // CreateCollection creates version 1 of a logical collection stream. + // All referenced artifacts MUST already exist. + rpc CreateCollection(CreateCollectionRequest) returns (Collection) { + option (google.api.http) = { + post: "/v1/publisher/collections" + body: "*" + }; + } + + // UpdateCollection creates a new immutable version of an existing collection. + // Prior versions remain addressable. + rpc UpdateCollection(UpdateCollectionRequest) returns (Collection) { + option (google.api.http) = { + post: "/v1/publisher/collections/{uuid}/versions" + body: "*" + }; + } + + // SignCollection signs one collection version and returns the updated + // collection metadata. + rpc SignCollection(SignCollectionRequest) returns (Collection) { + option (google.api.http) = { + post: "/v1/publisher/collections/{uuid}/sign" + body: "*" + }; + } + + // ========================================================================== + // Batch Operations + // ========================================================================== + + // BatchUploadArtifacts uploads multiple artifacts in one session. + // This is an advanced optional publisher capability. + rpc BatchUploadArtifacts(stream BatchUploadArtifactsRequest) returns (BatchUploadArtifactsResponse) { + option (google.api.http) = { + post: "/v1/publisher/artifacts/batch" + body: "*" + }; + } + + // ImportCollection imports complete collection + artifact data. + // This is an advanced optional publisher capability used for bulk migration. + rpc ImportCollection(stream ImportCollectionRequest) returns (ImportCollectionResponse) { + option (google.api.http) = { + post: "/v1/publisher/import" + body: "*" + }; + } +} + +// ============================================================================ +// Product Request/Response Messages +// ============================================================================ + +// CreateProductRequest is the request to create a product. +message CreateProductRequest { + // Human-readable name. + string name = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).string.max_len = 512, + (buf.validate.field).required = true + ]; + + // Optional description. + string description = 2; + + // Identifiers for the product. + repeated Identifier identifiers = 3; + + // Vendor information. + Vendor vendor = 4; + + // Optional homepage URL. + string homepage_url = 5 [json_name = "homepageUrl"]; + + // Optional documentation URL. + string documentation_url = 6 [json_name = "documentationUrl"]; + + // Optional VCS URL. + string vcs_url = 7 [json_name = "vcsUrl"]; + + // Client-provided UUID (optional, server generates if not provided). + optional string uuid = 8 [(buf.validate.field).string.uuid = true]; +} + +// UpdateProductRequest is the request to update a product. +message UpdateProductRequest { + // UUID of the product to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated name. + string name = 3; + + // Updated description. + string description = 4; + + // Updated identifiers (replaces existing). + repeated Identifier identifiers = 5; + + // Updated vendor information. + Vendor vendor = 6; + + // Updated homepage URL. + string homepage_url = 7 [json_name = "homepageUrl"]; + + // Updated documentation URL. + string documentation_url = 8 [json_name = "documentationUrl"]; + + // Updated VCS URL. + string vcs_url = 9 [json_name = "vcsUrl"]; +} + +// DeleteProductRequest is the request to delete a product. +message DeleteProductRequest { + // UUID of the product to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // If true, delete all releases as well. + bool cascade = 2; +} + +// DeleteProductResponse is the response from deleting a product. +message DeleteProductResponse { + // UUID of the deleted product. + string uuid = 1; + + // Number of releases deleted (if cascade=true). + int32 releases_deleted = 2 [json_name = "releasesDeleted"]; +} + +// CreateProductReleaseRequest is the request to create a product release. +message CreateProductReleaseRequest { + // UUID of the parent product (optional). + optional string product_uuid = 1 [(buf.validate.field).string.uuid = true, json_name = "productUuid"]; + + // Version string. + string version = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).required = true + ]; + + // Release date. + google.protobuf.Timestamp release_date = 3 [json_name = "releaseDate"]; + + // Pre-release flag. + bool pre_release = 4 [json_name = "preRelease"]; + + // Identifiers for the release. + repeated Identifier identifiers = 5; + + // Component references. + repeated ComponentRef components = 6; + + // Client-provided UUID (optional). + optional string uuid = 7 [(buf.validate.field).string.uuid = true]; +} + +// UpdateProductReleaseRequest is the request to update a product release. +message UpdateProductReleaseRequest { + // UUID of the product release to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated version. + string version = 3; + + // Updated release date. + google.protobuf.Timestamp release_date = 4 [json_name = "releaseDate"]; + + // Pre-release flag (can only change from true to false). + bool pre_release = 5 [json_name = "preRelease"]; + + // Updated identifiers. + repeated Identifier identifiers = 6; + + // Updated component references. + repeated ComponentRef components = 7; +} + +// DeleteProductReleaseRequest is the request to delete a product release. +message DeleteProductReleaseRequest { + // UUID of the product release to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// DeleteProductReleaseResponse is the response from deleting a product release. +message DeleteProductReleaseResponse { + // UUID of the deleted product release. + string uuid = 1; +} + +// ============================================================================ +// Component Request/Response Messages +// ============================================================================ + +// CreateComponentRequest is the request to create a component. +message CreateComponentRequest { + // Human-readable name. + string name = 1 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).required = true + ]; + + // Optional description. + string description = 2; + + // Identifiers for the component. + repeated Identifier identifiers = 3; + + // Component type. + ComponentType component_type = 4 [json_name = "componentType"]; + + // License information. + repeated LicenseInfo licenses = 5; + + // Publisher name. + string publisher = 6; + + // Homepage URL. + string homepage_url = 7 [json_name = "homepageUrl"]; + + // VCS URL. + string vcs_url = 8 [json_name = "vcsUrl"]; + + // Client-provided UUID (optional). + optional string uuid = 9 [(buf.validate.field).string.uuid = true]; +} + +// UpdateComponentRequest is the request to update a component. +message UpdateComponentRequest { + // UUID of the component to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated name. + string name = 3; + + // Updated description. + string description = 4; + + // Updated identifiers. + repeated Identifier identifiers = 5; + + // Updated component type. + ComponentType component_type = 6 [json_name = "componentType"]; + + // Updated licenses. + repeated LicenseInfo licenses = 7; + + // Updated publisher. + string publisher = 8; + + // Updated homepage URL. + string homepage_url = 9 [json_name = "homepageUrl"]; + + // Updated VCS URL. + string vcs_url = 10 [json_name = "vcsUrl"]; +} + +// DeleteComponentRequest is the request to delete a component. +message DeleteComponentRequest { + // UUID of the component to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // If true, delete all releases as well. + bool cascade = 2; +} + +// DeleteComponentResponse is the response from deleting a component. +message DeleteComponentResponse { + // UUID of the deleted component. + string uuid = 1; + + // Number of releases deleted (if cascade=true). + int32 releases_deleted = 2 [json_name = "releasesDeleted"]; +} + +// CreateComponentReleaseRequest is the request to create a component release. +message CreateComponentReleaseRequest { + // UUID of the parent component. + string component_uuid = 1 [ + (buf.validate.field).string.uuid = true, + (buf.validate.field).required = true, + json_name = "componentUuid" + ]; + + // Version string. + string version = 2 [ + (buf.validate.field).string.min_len = 1, + (buf.validate.field).required = true + ]; + + // Release date. + google.protobuf.Timestamp release_date = 3 [json_name = "releaseDate"]; + + // Pre-release flag. + bool pre_release = 4 [json_name = "preRelease"]; + + // Identifiers for the release. + repeated Identifier identifiers = 5; + + // Distributions. + repeated Distribution distributions = 6; + + // Client-provided UUID (optional). + optional string uuid = 7 [(buf.validate.field).string.uuid = true]; +} + +// UpdateComponentReleaseRequest is the request to update a component release. +message UpdateComponentReleaseRequest { + // UUID of the component release to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Fields to update. + google.protobuf.FieldMask update_mask = 2 [json_name = "updateMask"]; + + // Updated version. + string version = 3; + + // Updated release date. + google.protobuf.Timestamp release_date = 4 [json_name = "releaseDate"]; + + // Pre-release flag (can only change from true to false). + bool pre_release = 5 [json_name = "preRelease"]; + + // Updated identifiers. + repeated Identifier identifiers = 6; + + // Updated distributions. + repeated Distribution distributions = 7; +} + +// DeleteComponentReleaseRequest is the request to delete a component release. +message DeleteComponentReleaseRequest { + // UUID of the component release to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; +} + +// DeleteComponentReleaseResponse is the response from deleting a component release. +message DeleteComponentReleaseResponse { + // UUID of the deleted component release. + string uuid = 1; +} + +// ============================================================================ +// Artifact Request/Response Messages +// ============================================================================ + +// UploadArtifactRequest is used for streaming artifact uploads. +message UploadArtifactRequest { + // Streaming payload frames for an artifact upload. + oneof data { + // Metadata (must be sent first). + ArtifactMetadata metadata = 1; + + // Content chunk. + bytes content = 2; + } +} + +// ArtifactMetadata contains metadata for artifact upload or URL registration. +message ArtifactMetadata { + // Human-readable name. + string name = 1 [(buf.validate.field).required = true]; + + // Artifact type. + ArtifactType type = 2 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}]; + + // MIME type of the content. + string mime_type = 3 [json_name = "mimeType", (buf.validate.field).required = true]; + + // Optional description. + string description = 4; + + // Distribution types this applies to. + repeated string component_distributions = 5 [json_name = "componentDistributions"]; + + // Subject of the artifact. + ArtifactSubject subject = 6; + + // Spec version (e.g., "1.5" for CycloneDX). + string spec_version = 7 [json_name = "specVersion"]; + + // Client-provided UUID (optional). + optional string uuid = 8 [(buf.validate.field).string.uuid = true]; + + // Expected checksums for verification. + // Implementations SHOULD verify uploaded or fetched content against these + // values before persisting the artifact record. + repeated Checksum expected_checksums = 9 [json_name = "expectedChecksums"]; +} + +// CreateArtifactFromUrlRequest creates an artifact from an external URL. +message CreateArtifactFromUrlRequest { + // Artifact metadata. + ArtifactMetadata metadata = 1 [(buf.validate.field).required = true]; + + // URL to fetch the content from. + // This SHOULD point to immutable content. Implementations MAY enforce HTTPS + // and MAY reject private-network targets. + string source_url = 2 [ + (buf.validate.field).string.uri = true, + (buf.validate.field).required = true, + json_name = "sourceUrl" + ]; + + // Expected checksums for verification. + // If both metadata.expected_checksums and this field are populated, they MUST + // describe the same checksum set or the request should be rejected. + repeated Checksum expected_checksums = 3 [json_name = "expectedChecksums"]; + + // Optional signature URL. + string signature_url = 4 [json_name = "signatureUrl"]; +} + +// DeleteArtifactRequest is the request to delete an artifact. +message DeleteArtifactRequest { + // UUID of the artifact to delete. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Force deletion even if artifact is in use. + // Implementations MAY reject this flag when referential cleanup is not + // supported safely. + bool force = 2; +} + +// DeleteArtifactResponse is the response from deleting an artifact. +message DeleteArtifactResponse { + // UUID of the deleted artifact. + string uuid = 1; + + // Collections that referenced this artifact (if forced). + repeated string affected_collection_uuids = 2 [json_name = "affectedCollectionUuids"]; +} + +// ============================================================================ +// Collection Request/Response Messages +// ============================================================================ + +// CreateCollectionRequest is the request to create a collection. +message CreateCollectionRequest { + // Logical collection UUID. + // For release-scoped collections this is typically the referenced release + // UUID and remains stable across collection versions. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Scope of the collection. + CollectionScope belongs_to = 2 [json_name = "belongsTo", (buf.validate.field).required = true]; + + // UUIDs of artifacts to include in version 1. + repeated string artifact_uuids = 3 [json_name = "artifactUuids"]; + + // Update reason (typically INITIAL_RELEASE for version 1). + UpdateReason update_reason = 4 [json_name = "updateReason"]; +} + +// UpdateCollectionRequest creates a new version of a collection. +message UpdateCollectionRequest { + // UUID of the collection to update. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Full replacement set of artifacts in the new version. + repeated string artifact_uuids = 2 [json_name = "artifactUuids"]; + + // Reason for the update. + UpdateReason update_reason = 3 [json_name = "updateReason", (buf.validate.field).required = true]; +} + +// SignCollectionRequest signs a collection version. +message SignCollectionRequest { + // UUID of the collection to sign. + string uuid = 1 [(buf.validate.field).string.uuid = true, (buf.validate.field).required = true]; + + // Version to sign (latest if omitted). + optional int32 version = 2; + + // Key ID to use for signing. + string key_id = 3 [json_name = "keyId"]; + + // Use Sigstore for signing. + bool use_sigstore = 4 [json_name = "useSigstore"]; +} + +// ============================================================================ +// Batch Operations +// ============================================================================ + +// BatchUploadArtifactsRequest is used for batch artifact uploads. +message BatchUploadArtifactsRequest { + // Streaming payload frames for a batch upload. + oneof data { + // Batch metadata (must be sent first). + BatchArtifactMetadata batch_metadata = 1; + + // Single artifact data. + UploadArtifactRequest artifact = 2; + } +} + +// BatchArtifactMetadata contains metadata for batch upload. +message BatchArtifactMetadata { + // Total number of artifacts in this batch. + int32 total_count = 1 [json_name = "totalCount"]; + + // Target collection UUID (optional). + optional string collection_uuid = 2 [json_name = "collectionUuid"]; +} + +// BatchUploadArtifactsResponse contains results of batch upload. +message BatchUploadArtifactsResponse { + // Successfully uploaded artifacts. + repeated Artifact artifacts = 1; + + // Failed uploads. + repeated BatchUploadError errors = 2; +} + +// BatchUploadError describes a failed upload in a batch. +message BatchUploadError { + // Index in the batch. + int32 index = 1; + + // Artifact name (if available). + string name = 2; + + // Error details. + ErrorDetail error = 3; +} + +// ImportCollectionRequest is used for bulk import. +message ImportCollectionRequest { + // Streaming payload frames for a collection import. + oneof data { + // Import metadata (must be sent first). + ImportMetadata import_metadata = 1; + + // Collection data. + Collection collection = 2; + + // Artifact with content. + ArtifactWithContent artifact = 3; + } +} + +// ImportMetadata contains metadata for bulk import. +message ImportMetadata { + // Source system identifier. + string source_system = 1 [json_name = "sourceSystem"]; + + // Total collections in import. + int32 total_collections = 2 [json_name = "totalCollections"]; + + // Total artifacts in import. + int32 total_artifacts = 3 [json_name = "totalArtifacts"]; + + // Overwrite existing data. + bool overwrite = 4; +} + +// ArtifactWithContent includes artifact metadata and content. +message ArtifactWithContent { + // Artifact metadata. + Artifact artifact = 1; + + // Content for each format. + repeated ArtifactFormatContent format_contents = 2 [json_name = "formatContents"]; +} + +// ArtifactFormatContent contains content for one format. +message ArtifactFormatContent { + // MIME type. + string mime_type = 1 [json_name = "mimeType"]; + + // Content bytes. + bytes content = 2; +} + +// ImportCollectionResponse contains results of bulk import. +message ImportCollectionResponse { + // Number of collections imported. + int32 collections_imported = 1 [json_name = "collectionsImported"]; + + // Number of artifacts imported. + int32 artifacts_imported = 2 [json_name = "artifactsImported"]; + + // Errors during import. + repeated ImportError errors = 3; +} + +// ImportError describes an error during import. +message ImportError { + // Type of entity that failed. + string entity_type = 1 [json_name = "entityType"]; + + // UUID of the entity. + string uuid = 2; + + // Error details. + ErrorDetail error = 3; +} 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 From f42bda41da28be98ca634f32efb35f2026d1d51b Mon Sep 17 00:00:00 2001 From: Mohamed Chorfa Date: Sun, 29 Mar 2026 19:11:22 -0400 Subject: [PATCH 2/4] spec: drop implementation-only migration and proto tooling files Signed-off-by: Mohamed Chorfa --- doc/migration-guide.md | 178 ----------------------------------------- proto/.gitignore | 16 ---- proto/Makefile | 114 -------------------------- proto/buf.gen.yaml | 30 ------- proto/buf.lock | 12 --- 5 files changed, 350 deletions(-) delete mode 100644 doc/migration-guide.md delete mode 100644 proto/.gitignore delete mode 100644 proto/Makefile delete mode 100644 proto/buf.gen.yaml delete mode 100644 proto/buf.lock diff --git a/doc/migration-guide.md b/doc/migration-guide.md deleted file mode 100644 index 1a622fd..0000000 --- a/doc/migration-guide.md +++ /dev/null @@ -1,178 +0,0 @@ -# Migration Guide - -This guide provides instructions for migrating between versions of the Transparency Exchange API (TEA). Follow these steps carefully to ensure compatibility and minimize downtime. - -## Version Compatibility Matrix - -| From Version | To Version | Migration Path | Breaking Changes | -| ------------ | ---------- | ----------------------- | --------------------- | -| 0.1.x | 0.2.x | Direct | None | -| 0.2.x | 0.3.x | Direct | None | -| 0.3.x | 1.0.0 | Direct | None (beta to stable) | -| 1.0.x | 1.1.x | Direct | None | -| 1.x.x | 2.0.0 | Review breaking changes | Yes | - -## General Migration Steps - -### 1. Review Release Notes - -Before migrating, carefully review the release notes for the target version: - -- New features and endpoints -- Deprecated functionality -- Required configuration changes -- Known issues and workarounds - -### 2. Update Dependencies - -Update TEA client libraries and dependencies to compatible versions: - -```bash -# Example for different languages -npm update @cyclonedx/tea-client -pip install --upgrade cyclonedx-tea -cargo update cyclonedx-tea -``` - -### 3. Update Configuration - -Review and update configuration files: - -```yaml -# config.yaml -api: - version: "1.0.0" # Update to target version - baseUrl: "https://api.example.com/tea/v1" -``` - -### 4. Update Code - -Modify client code for any API changes: - -```javascript -// Before (v0.3.x) -const client = new TeaClient({ - baseUrl: 'https://api.example.com/tea' -}); - -// After (v1.0.0) -const client = new TeaClient({ - baseUrl: 'https://api.example.com/tea/v1', - version: '1.0.0' -}); -``` - -### 5. Test Migration - -Test the migration in a staging environment: - -```bash -# Run integration tests -npm test -pytest tests/ -cargo test -``` - -### 6. Deploy Gradually - -Use canary deployments or feature flags: - -```bash -# Deploy to 10% of traffic first -kubectl set image deployment/tea-client tea-client:v1.0.0 -kubectl rollout status deployment/tea-client -``` - -## Version-Specific Migrations - -### Migrating from 0.3.x to 1.0.0 - -#### API Changes - -- No breaking changes - this is a stability release -- All beta features are now stable -- Improved error messages and validation - -#### Client Changes - -```typescript -// Update version specification -const client = new TeaClient({ - version: '1.0.0' // Explicitly specify version -}); -``` - -#### Server Changes - -- Update server configuration for stable endpoints -- Review authentication settings -- Update monitoring and logging - -### Migrating from 1.0.x to 1.1.x - -#### New Features - -- Enhanced insights API -- Improved pagination support -- Additional artifact types - -#### Backward Compatibility - -- All existing clients continue to work -- New features are opt-in - -#### Optional Updates - -```python -# Use new insights features -insights = client.insights.query("component.name == 'openssl'") -``` - -## Troubleshooting - -### Common Issues - -#### Authentication Failures - -- Verify token scopes are correct for v1 endpoints -- Check mTLS certificate validity -- Confirm issuer configuration - -#### Version Negotiation - -- Ensure client specifies correct API version -- Check server version support -- Review well-known endpoint configuration - -#### Performance Issues - -- Monitor rate limits after migration -- Check for N+1 query problems -- Optimize batch operations - -### Rollback Plan - -Always prepare a rollback strategy: - -```bash -# Quick rollback command -kubectl rollout undo deployment/tea-client -``` - -## Support - -For migration assistance: - -- Review the [TEA documentation](https://github.com/CycloneDX/transparency-exchange-api) -- Check the [GitHub issues](https://github.com/CycloneDX/transparency-exchange-api/issues) for known issues -- Contact the TEA working group for complex migrations - -## Post-Migration Validation - -After migration, validate: - -- [ ] All existing functionality works -- [ ] New features are accessible -- [ ] Performance meets requirements -- [ ] Monitoring and alerting are configured -- [ ] Documentation is updated diff --git a/proto/.gitignore b/proto/.gitignore deleted file mode 100644 index 61c84f5..0000000 --- a/proto/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Generated code -gen/ - -# Buf dependency cache -.buf-deps/ - -# IDE files -.idea/ -.vscode/ -*.swp -*.swo -*~ - -# OS files -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/proto/Makefile b/proto/Makefile deleted file mode 100644 index cea9c8f..0000000 --- a/proto/Makefile +++ /dev/null @@ -1,114 +0,0 @@ -# TEA Protobuf Makefile -# Common commands for working with TEA protobuf definitions - -.PHONY: all deps lint breaking generate generate-go generate-rust export-deps validate verify clean help - -# Default target -all: deps validate lint generate generate-rust - -# Install dependencies -deps: - @echo "==> Updating Buf dependencies..." - buf dep update - -# Lint protobuf files -lint: - @echo "==> Linting protobuf files..." - buf lint - -# Check for breaking changes against main branch -breaking: - @echo "==> Checking for breaking changes..." - buf breaking --against '.git#branch=main' - -# Check for breaking changes against published BSR module -breaking-bsr: - @echo "==> Checking for breaking changes against BSR..." - buf breaking --against buf.build/cyclonedx/tea - -# Generate Buf-managed code -generate: - @echo "==> Generating code..." - buf generate - @echo "==> Buf-managed code generated in gen/go/" - -# Generate only Go code -generate-go: - @echo "==> Generating Go code..." - buf generate --template buf.gen.yaml --include-imports \ - --path tea/v1/common.proto \ - --path tea/v1/product.proto \ - --path tea/v1/component.proto \ - --path tea/v1/collection.proto \ - --path tea/v1/artifact.proto \ - --path tea/v1/discovery.proto \ - --path tea/v1/consumer.proto \ - --path tea/v1/publisher.proto \ - --path tea/v1/insights.proto - -# Generate Rust bindings via tea-server's tonic-build pipeline -generate-rust: export-deps - @echo "==> Generating Rust bindings..." - cargo check --manifest-path ../tea-server/Cargo.toml --locked - @echo "==> Rust bindings generated via tea-server/build.rs" - -# Format protobuf files -format: - @echo "==> Formatting protobuf files..." - buf format -w - -# Export dependencies (for IDE support) -export-deps: - @echo "==> Exporting dependencies..." - rm -rf .buf-deps - buf export . --output .buf-deps - @echo "==> Dependencies exported to .buf-deps/" - -# Validate Buf workspace configuration -validate: - @echo "==> Validating buf workspace..." - buf config ls-modules >/dev/null - @echo "==> Buf workspace configuration is valid" - -# Build (lint + breaking check) -build: validate lint breaking - @echo "==> Build checks passed" - -# Full local verification for schema + generated artifacts -verify: validate lint generate generate-rust - @echo "==> Proto verification passed" - -# Push to Buf Schema Registry -push: - @echo "==> Pushing to Buf Schema Registry..." - buf push - -# Clean generated files -clean: - @echo "==> Cleaning generated files..." - rm -rf gen/ - rm -rf .buf-deps/ - -# Show help -help: - @echo "TEA Protobuf Makefile" - @echo "" - @echo "Usage: make [target]" - @echo "" - @echo "Targets:" - @echo " all - deps + validate + lint + generate + generate-rust (default)" - @echo " deps - Update Buf dependencies" - @echo " validate - Validate Buf workspace configuration" - @echo " lint - Lint protobuf files" - @echo " breaking - Check for breaking changes (vs main branch)" - @echo " breaking-bsr - Check for breaking changes (vs BSR)" - @echo " generate - Generate Buf-managed code (Go)" - @echo " generate-go - Generate only Go code" - @echo " generate-rust - Generate Rust bindings via tea-server/build.rs" - @echo " format - Format protobuf files" - @echo " export-deps - Export dependencies for IDE support" - @echo " verify - Validate + lint + generate + generate-rust" - @echo " build - Run lint + breaking checks" - @echo " push - Push to Buf Schema Registry" - @echo " clean - Remove generated files" - @echo " help - Show this help message" diff --git a/proto/buf.gen.yaml b/proto/buf.gen.yaml deleted file mode 100644 index 4558008..0000000 --- a/proto/buf.gen.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Buf V2 code generation configuration for TEA -# https://buf.build/docs/configuration/v2/buf-gen-yaml -version: v2 -clean: true - -# Code generation plugins -plugins: - # Go protobuf messages and gRPC stubs - - remote: buf.build/protocolbuffers/go:v1.36.5 - out: gen/go - opt: - - paths=source_relative - - # Go gRPC stubs - - remote: buf.build/grpc/go:v1.6.0 - out: gen/go - opt: - - paths=source_relative - - require_unimplemented_servers=false - -managed: - enabled: true - disable: - - file_option: go_package - module: buf.build/bufbuild/protovalidate -inputs: - # Generate from local proto files - - directory: . - exclude_paths: - - .buf-deps diff --git a/proto/buf.lock b/proto/buf.lock deleted file mode 100644 index 0a2ba2e..0000000 --- a/proto/buf.lock +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by buf. DO NOT EDIT. -version: v2 -deps: - - name: buf.build/bufbuild/protovalidate - commit: 2a1774d888024a9b93ce7eb4b59f6a83 - digest: b5:6b7f9bc919b65e5b79d7b726ffc03d6f815a412d6b792970fa6f065cae162107bd0a9d47272c8ab1a2c9514e87b13d3fbf71df614374d62d2183afb64be2d30a - - name: buf.build/googleapis/googleapis - commit: 004180b77378443887d3b55cabc00384 - digest: b5:e8f475fe3330f31f5fd86ac689093bcd274e19611a09db91f41d637cb9197881ce89882b94d13a58738e53c91c6e4bae7dc1feba85f590164c975a89e25115dc - - name: buf.build/grpc-ecosystem/grpc-gateway - commit: 6467306b4f624747aaf6266762ee7a1c - digest: b5:c2caa61467d992749812c909f93c07e9a667da33c758a7c1973d63136c23b3cafcc079985b12cdf54a10049ed3297418f1eda42cdffdcf34113792dcc3a990af From 38782dc299db908cbea1d8d3f81227f11d945f35 Mon Sep 17 00:00:00 2001 From: Mohamed Chorfa Date: Sun, 29 Mar 2026 19:14:46 -0400 Subject: [PATCH 3/4] spec: drop buf workspace metadata from the PR Signed-off-by: Mohamed Chorfa --- proto/buf.yaml | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 proto/buf.yaml diff --git a/proto/buf.yaml b/proto/buf.yaml deleted file mode 100644 index 9f27686..0000000 --- a/proto/buf.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Buf V2 module configuration for TEA (Transparency Exchange API) -# https://buf.build/docs/configuration/v2/buf-yaml -version: v2 - -modules: - - path: . - name: buf.build/cyclonedx/tea - excludes: - - .buf-deps - -# Dependencies from Buf Schema Registry -deps: - - buf.build/bufbuild/protovalidate - # Google API annotations for gRPC-Gateway HTTP bindings - - buf.build/googleapis/googleapis - # gRPC-Gateway annotations - - buf.build/grpc-ecosystem/grpc-gateway -# Linting configuration -lint: - use: - - STANDARD - - COMMENTS - except: - # Allow v1 instead of requiring v1beta1 for initial release - - PACKAGE_VERSION_SUFFIX - # TEA intentionally returns canonical resource messages directly for many - # read/write RPCs, so the standard request/response wrapper naming rules - # are too restrictive for this schema. - - RPC_REQUEST_RESPONSE_UNIQUE - - RPC_REQUEST_STANDARD_NAME - - RPC_RESPONSE_STANDARD_NAME - disallow_comment_ignores: true - enum_zero_value_suffix: _UNSPECIFIED - rpc_allow_same_request_response: false - rpc_allow_google_protobuf_empty_requests: true - rpc_allow_google_protobuf_empty_responses: true - service_suffix: Service - -# Breaking change detection -breaking: - use: - - FILE - except: - # Allow during beta development - - FIELD_SAME_DEFAULT - ignore_unstable_packages: true From 3b1d0501216cee4ea73e4d84c9b8e8ecacc6427a Mon Sep 17 00:00:00 2001 From: Mohamed Chorfa Date: Mon, 30 Mar 2026 09:58:53 -0400 Subject: [PATCH 4/4] spec: isolate publisher and insights service drafts Signed-off-by: Mohamed Chorfa --- api-flow/consumer.md | 43 +- doc/authentication.md | 97 -- doc/authorization.md | 98 -- doc/tea-requirements.md | 12 +- doc/tea-usecases.md | 14 +- doc/tea_consumer_flow.excalidraw | 1092 ---------------- doc/tea_data_model.excalidraw | 1015 --------------- doc/tea_discovery_security.excalidraw | 1103 ----------------- doc/tea_publisher_flow.excalidraw | 948 -------------- doc/versioning.md | 146 --- graph-schema.json | 73 -- proto/tea/v1/artifact.proto | 364 ------ proto/tea/v1/collection.proto | 316 ----- proto/tea/v1/common.proto | 371 ------ proto/tea/v1/component.proto | 334 ----- proto/tea/v1/consumer.proto | 386 ------ proto/tea/v1/discovery.proto | 360 ------ proto/tea/v1/product.proto | 302 ----- .../json/artifact/artifact-format.schema.json | 57 - schemas/json/artifact/artifact.schema.json | 116 -- .../json/collection/collection.schema.json | 61 - .../json/collection/update-reason.schema.json | 15 - schemas/json/common/checksum.schema.json | 40 - schemas/json/common/error.schema.json | 89 -- schemas/json/common/identifier.schema.json | 35 - schemas/json/common/pagination.schema.json | 29 - .../component/component-release.schema.json | 53 - schemas/json/component/component.schema.json | 74 -- .../json/component/distribution.schema.json | 48 - schemas/json/component/license.schema.json | 33 - .../discovery/discovery-response.schema.json | 77 -- .../json/product/component-ref.schema.json | 24 - schemas/json/product/contact.schema.json | 30 - .../json/product/product-release.schema.json | 52 - schemas/json/product/product.schema.json | 65 - schemas/json/product/vendor.schema.json | 32 - 36 files changed, 14 insertions(+), 7990 deletions(-) delete mode 100644 doc/authentication.md delete mode 100644 doc/authorization.md delete mode 100644 doc/tea_consumer_flow.excalidraw delete mode 100644 doc/tea_data_model.excalidraw delete mode 100644 doc/tea_discovery_security.excalidraw delete mode 100644 doc/tea_publisher_flow.excalidraw delete mode 100644 doc/versioning.md delete mode 100644 graph-schema.json delete mode 100644 proto/tea/v1/artifact.proto delete mode 100644 proto/tea/v1/collection.proto delete mode 100644 proto/tea/v1/common.proto delete mode 100644 proto/tea/v1/component.proto delete mode 100644 proto/tea/v1/consumer.proto delete mode 100644 proto/tea/v1/discovery.proto delete mode 100644 proto/tea/v1/product.proto delete mode 100644 schemas/json/artifact/artifact-format.schema.json delete mode 100644 schemas/json/artifact/artifact.schema.json delete mode 100644 schemas/json/collection/collection.schema.json delete mode 100644 schemas/json/collection/update-reason.schema.json delete mode 100644 schemas/json/common/checksum.schema.json delete mode 100644 schemas/json/common/error.schema.json delete mode 100644 schemas/json/common/identifier.schema.json delete mode 100644 schemas/json/common/pagination.schema.json delete mode 100644 schemas/json/component/component-release.schema.json delete mode 100644 schemas/json/component/component.schema.json delete mode 100644 schemas/json/component/distribution.schema.json delete mode 100644 schemas/json/component/license.schema.json delete mode 100644 schemas/json/discovery/discovery-response.schema.json delete mode 100644 schemas/json/product/component-ref.schema.json delete mode 100644 schemas/json/product/contact.schema.json delete mode 100644 schemas/json/product/product-release.schema.json delete mode 100644 schemas/json/product/product.schema.json delete mode 100644 schemas/json/product/vendor.schema.json diff --git a/api-flow/consumer.md b/api-flow/consumer.md index 3451ecb..1bd7137 100644 --- a/api-flow/consumer.md +++ b/api-flow/consumer.md @@ -23,18 +23,8 @@ Component Release) to find the list of artefacts for the particular Product Rele ## API flow based on TEI discovery ```mermaid ---- -config: - sequence: - diagramMarginX: 60 - diagramMarginY: 40 - actorFontSize: 20 - actorFontWeight: bold - noteFontSize: 18 - theme: neo dark - layout: elk - look: neo +--- title: TEA consumer flow --- sequenceDiagram @@ -70,6 +60,7 @@ sequenceDiagram user ->> tea_component_release: Obtain latest collections tea_component_release -->> user: List of TEA Artifacts end + ``` ## API flow based on direct access to API @@ -79,16 +70,6 @@ In this case, the client wants to search for a specific product release using th ```mermaid --- -config: - sequence: - diagramMarginX: 60 - diagramMarginY: 40 - actorFontSize: 20 - actorFontWeight: bold - noteFontSize: 18 - theme: neo dark - layout: elk - look: neo title: TEA client flow with search --- @@ -128,16 +109,6 @@ for a release. ```mermaid --- -config: - sequence: - diagramMarginX: 60 - diagramMarginY: 40 - actorFontSize: 20 - actorFontWeight: bold - noteFontSize: 18 - theme: neo dark - layout: elk - look: neo title: TEA client flow with direct query for release --- @@ -166,16 +137,6 @@ another query is done to get reason for update and new collection list of artefa ```mermaid --- -config: - sequence: - diagramMarginX: 60 - diagramMarginY: 40 - actorFontSize: 20 - actorFontWeight: bold - noteFontSize: 18 - theme: neo - layout: elk - look: neo title: TEA client collection query --- diff --git a/doc/authentication.md b/doc/authentication.md deleted file mode 100644 index 13f5bf6..0000000 --- a/doc/authentication.md +++ /dev/null @@ -1,97 +0,0 @@ -# Authentication - -The Transparency Exchange API (TEA) supports two primary authentication mechanisms: Bearer Token authentication and Mutual TLS (mTLS) authentication. Implementations MUST support at least one of these mechanisms, and SHOULD support both for maximum compatibility. - -## Bearer Token Authentication - -Bearer token authentication uses JSON Web Tokens (JWTs) issued by an external identity provider. This is the recommended authentication mechanism for most use cases. - -### Token Acquisition - -Tokens are acquired out-of-band from the TEA server. The exact process depends on the service provider's identity management system, but typically involves: - -1. User or service authenticates with the provider's portal or API -2. Provider issues a short-lived JWT (recommended: < 1 hour) -3. Client includes the token in API requests - -### Token Format - -Tokens MUST be valid JWTs conforming to RFC 7519. The token payload SHOULD include: - -- `iss`: Issuer identifier -- `sub`: Subject (user or service identifier) -- `aud`: Audience (TEA server identifier) -- `exp`: Expiration time -- `iat`: Issued at time -- `scope`: Space-separated list of authorized scopes - -### Request Format - -Include the token in the `Authorization` header: - -``` -Authorization: Bearer -``` - -### Token Validation - -Servers MUST validate: - -- Token signature using the issuer's public key -- Token expiration (`exp` claim) -- Token audience (`aud` claim) -- Required scopes for the operation - -## Mutual TLS Authentication - -Mutual TLS authentication uses client certificates for mutual authentication between client and server. - -### Certificate Requirements - -- Client certificates MUST use ECDSA P-384 or Ed25519 algorithms -- Certificates MUST be issued by a trusted Certificate Authority (CA) -- Certificate Subject Alternative Name (SAN) MUST include the client identifier - -### TLS Configuration - -- Minimum TLS version: 1.3 -- Server MUST request client certificates -- Server MUST validate certificate chain -- Client MUST present valid certificate - -### Authorization Mapping - -The client certificate's subject or SAN is mapped to TEA identities and scopes through server-side configuration. - -## Authentication Flow - -```mermaid -sequenceDiagram - participant Client - participant TEA Server - participant IdP - - alt Bearer Token - Client->>IdP: Acquire token - IdP-->>Client: JWT token - Client->>TEA Server: Request + Authorization: Bearer - TEA Server->>TEA Server: Validate token - else mTLS - Client->>TEA Server: TLS handshake with client cert - TEA Server->>TEA Server: Validate certificate - end - - TEA Server-->>Client: Authenticated response -``` - -## Error Responses - -- `401 Unauthorized`: Missing or invalid credentials -- `403 Forbidden`: Valid credentials but insufficient permissions - -## Security Considerations - -- Tokens SHOULD be short-lived (< 1 hour) -- mTLS certificates SHOULD have short validity periods -- Implement token revocation mechanisms -- Log authentication failures for security monitoring diff --git a/doc/authorization.md b/doc/authorization.md deleted file mode 100644 index a8a317f..0000000 --- a/doc/authorization.md +++ /dev/null @@ -1,98 +0,0 @@ -# Authorization - -The Transparency Exchange API (TEA) uses a scope-based authorization model. Permissions are granted through scopes assigned to authenticated identities. Scopes follow a hierarchical structure with read operations generally requiring fewer permissions than write operations. - -## Scope Model - -Scopes are granted to identities during authentication. Bearer tokens include scopes in the `scope` claim as a space-separated list. mTLS certificates are mapped to scopes through server configuration. - -### Scope Hierarchy - -``` -SCOPE_ADMIN_FULL (admin) -├── SCOPE_PUBLISHER_* (publisher) -│ ├── SCOPE_PUBLISHER_PRODUCTS_WRITE -│ ├── SCOPE_PUBLISHER_COMPONENTS_WRITE -│ ├── SCOPE_PUBLISHER_RELEASES_WRITE -│ ├── SCOPE_PUBLISHER_ARTIFACTS_WRITE -│ └── SCOPE_PUBLISHER_COLLECTIONS_WRITE -└── SCOPE_CONSUMER_* (consumer) - ├── SCOPE_CONSUMER_PRODUCTS_READ - ├── SCOPE_CONSUMER_COMPONENTS_READ - ├── SCOPE_CONSUMER_COLLECTIONS_READ - ├── SCOPE_CONSUMER_ARTIFACTS_READ - ├── SCOPE_CONSUMER_ARTIFACTS_DOWNLOAD - └── SCOPE_CONSUMER_INSIGHTS_QUERY -``` - -## Consumer Scopes - -### Read Operations - -- `SCOPE_CONSUMER_PRODUCTS_READ`: List and retrieve product metadata -- `SCOPE_CONSUMER_COMPONENTS_READ`: List and retrieve component metadata -- `SCOPE_CONSUMER_COLLECTIONS_READ`: Access collection metadata and versions -- `SCOPE_CONSUMER_ARTIFACTS_READ`: Retrieve artifact metadata -- `SCOPE_CONSUMER_ARTIFACTS_DOWNLOAD`: Download artifact content -- `SCOPE_CONSUMER_INSIGHTS_QUERY`: Execute CEL-based queries and searches - -## Publisher Scopes - -### Write Operations - -- `SCOPE_PUBLISHER_PRODUCTS_WRITE`: Create, update, delete products -- `SCOPE_PUBLISHER_COMPONENTS_WRITE`: Create, update, delete components -- `SCOPE_PUBLISHER_RELEASES_WRITE`: Create, update product/component releases -- `SCOPE_PUBLISHER_ARTIFACTS_WRITE`: Upload and delete artifacts -- `SCOPE_PUBLISHER_COLLECTIONS_WRITE`: Create and update collections - -## Admin Scopes - -### Administrative Operations - -- `SCOPE_ADMIN_FULL`: Full administrative access including user management, system configuration, and audit functions - -## Authorization Logic - -### API Endpoint Requirements - -| Endpoint | Required Scopes | -| ----------------------------- | ----------------------------------- | -| `GET /.well-known/tea` | None (public) | -| `GET /v1/discovery` | None (public) | -| `GET /v1/products*` | `SCOPE_CONSUMER_PRODUCTS_READ` | -| `GET /v1/components*` | `SCOPE_CONSUMER_COMPONENTS_READ` | -| `GET /v1/collections*` | `SCOPE_CONSUMER_COLLECTIONS_READ` | -| `GET /v1/artifacts*` | `SCOPE_CONSUMER_ARTIFACTS_READ` | -| `GET /v1/artifacts/*/content` | `SCOPE_CONSUMER_ARTIFACTS_DOWNLOAD` | -| `POST /v1/insights/*` | `SCOPE_CONSUMER_INSIGHTS_QUERY` | -| `POST /v1/publisher/*` | Publisher scopes as above | - -### Scope Validation - -Servers MUST validate that the authenticated identity possesses all required scopes for the requested operation. Missing scopes result in `403 Forbidden` responses. - -### Scope Granularity - -Scopes are intentionally coarse-grained to simplify implementation. Fine-grained access control (e.g., per-object permissions) is not supported in the base specification but may be implemented as extensions. - -## Authorization Flow - -```mermaid -flowchart TD - A[Request] --> B[Authenticate] - B --> C{Valid Identity?} - C -->|No| D[401 Unauthorized] - C -->|Yes| E[Extract Scopes] - E --> F{Required Scopes?} - F -->|No| G[403 Forbidden] - F -->|Yes| H[Process Request] - H --> I[Response] -``` - -## Security Considerations - -- Implement least privilege: grant only necessary scopes -- Regularly rotate credentials and review scope assignments -- Audit authorization decisions for compliance -- Consider scope expiration for temporary access diff --git a/doc/tea-requirements.md b/doc/tea-requirements.md index 2f8b070..c60a1e6 100644 --- a/doc/tea-requirements.md +++ b/doc/tea-requirements.md @@ -1,7 +1,6 @@ # TEA Requirements ## Repository discovery - Based on an identifier a repository URL needs to be found. The identifier can be: - PURL @@ -35,9 +34,6 @@ Collections are OPTIONAL. - VDR - Vulnerability Disclosure Report - VEX - Vulnerability Exploitability eXchange - CDXA - Attestation -- ML-BOM - Machine Learning Bill of Material (profile of SBOM) -- DATA-BOM - Data Bill of Material (profile of SBOM) -- AI-BOM - Artificial Intelligence Bill of Material (profile of SBOM) Authn/Authz MUST be supported @@ -65,7 +61,7 @@ Authn/Authz MUST be supported ## Artefact Publishing -The API MUST provide a way to publish an artefact, either standalone or to a collection. +The API MUST provide a way to publish an artefact, either standalone or to a collection. The detection of duplicate artefacts with the same identity MUST be handled and prevented. Authn/Authz MUST be supported @@ -89,11 +85,11 @@ PURL, CPE, SWID, GAV, GTIN, and GMN. For example: -- Return the identity of all BOMs that have a vulnerable version of Apache Log4J: +- Return the identity of all BOMs that have a vulnerable version of Apache Log4J: `pkg:maven/org.apache.logging.log4j/log4j-core@2.10.0` -The API MUST provide a way to search for the metadata component across all available BOMs. -The API SHOULD support multiple identity formats including PURL, CPE, SWID, GAV, GTIN, and GMN. +The API MUST provide a way to search for the metadata component across all available BOMs. +The API SHOULD support multiple identity formats including PURL, CPE, SWID, GAV, GTIN, and GMN. For example: - Return the identity of all artefacts that describe `cpe:/a:acme:commerce_suite:1.0`. diff --git a/doc/tea-usecases.md b/doc/tea-usecases.md index 916a6a9..bd3b702 100644 --- a/doc/tea-usecases.md +++ b/doc/tea-usecases.md @@ -6,10 +6,10 @@ a..." The use cases are divided in two categories: -* Use cases for __customers__ (end-users, manufacturers) to find a repository with +* Use cases for __customers__ (end-users, manufacturers) to find a repository with Transparency artefacts for a single unit purchased * Use cases where there are different __products__ - * This applies after discovery where we need to handle various things a customer may + * This applies after discovery where we need to handle various things a customer may buy as a single unit ## Customer focused use cases @@ -18,17 +18,18 @@ The use cases are divided in two categories: As a consumer that has an SBOM for a product, I want to be able to retrieve VEX and VDR files automatically both for current and old versions of the software. In the SBOM the product is identified by a PURL or other means (CPE, …) + ### C2: Consumer: Automation based on product name/identifier As a consumer, I want to download artefacts for a product based on known data. A combination of manufacturer, product name, vendor product ID, EAN bar code or other unique identifier. -After discovering the base repository URL I want to be able to find a specific +After discovering the base repository URL I want to be able to find a specific product variant and version. If the consumer is a business, then the procurement process may include delivery of an SBOM with proper identifiers and possibly URLs or identifiers in another document, which may bootstrap the discovery process in a more exact way than in the case of buying a product in a retail market. Alice bought a gadget at the gadget store that contains a full Linux system. Where and how will she find the SBOM and VEX for the gadget? -### C3: Consumer: Artefact retrieval +### C3: Consumer: Artefact retrieval As a consumer, I want to retrieve one or more supply chain artefacts for the products that I have access to, possibly through licensing or other means. As a consumer, I should be able to retrieve all source artefacts such as xBOMs, VDR/VEX, CDXA, and CLE. @@ -41,8 +42,8 @@ A CLE captures all lifecycle events over time, however, there is a need to retri As a consumer, I want the ability to simply ask the API questions rather than having to download, process, and analyze raw supply chain artefacts on my own systems. Common questions should be -provided by the API by default along with the ability to query for more complex answers using -the Common Expression Language (CEL). +provided by the API by default along with the ability to query for more complex answers using +the Common Expression Language (CEL). _NOTE_: Project Hyades (Dependency-Track v5) already implements CEL with the CycloneDX object model and has proven that this approach works for complex queries. @@ -81,6 +82,7 @@ They need to make an assessment before starting tests and possible production us The can either use the Debian package, the Alpine Linux Package or build a binary themselves from source code. How can they find the transparency exchange data sets? + ### O1: Open Source project The hatturl open source project publish a library and a server side software on Github. This is diff --git a/doc/tea_consumer_flow.excalidraw b/doc/tea_consumer_flow.excalidraw deleted file mode 100644 index 221943a..0000000 --- a/doc/tea_consumer_flow.excalidraw +++ /dev/null @@ -1,1092 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "version": 2, - "versionNonce": 623532812, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 313667311, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "text", - "id": "t_seq", - "x": 300, - "y": 850, - "text": "TEA Consumer & Discovery Flow", - "fontSize": 28, - "strokeColor": "#1e1e1e", - "width": 446.6, - "height": 35, - "baseline": 31, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "version": 2, - "versionNonce": 288208189, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 508448315, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "uHead2_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "uHead2", - "x": 50, - "y": 950, - "width": 120, - "height": 40, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#4a9eed", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "uHead2_text", - "x": 66, - "y": 960, - "width": 88, - "height": 20, - "text": "TEA Client", - "fontSize": 16, - "fontFamily": 1, - "containerId": "uHead2", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 709554373, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 442489299, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 16 - }, - { - "version": 2, - "versionNonce": 166791788, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 326732043, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "uLine2", - "x": 110, - "y": 990, - "width": 0, - "height": 550, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 550 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 442817095, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 45220990, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "ellipse", - "id": "uh2", - "x": 100, - "y": 900, - "width": 20, - "height": 20, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "strokeColor": "#4a9eed", - "strokeWidth": 2 - }, - { - "version": 2, - "versionNonce": 479732483, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 329452988, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "ub2", - "x": 100, - "y": 922, - "width": 20, - "height": 20, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#4a9eed", - "strokeWidth": 2 - }, - { - "version": 2, - "versionNonce": 367400102, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 499749119, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "dHead_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "dHead", - "x": 300, - "y": 950, - "width": 160, - "height": 40, - "backgroundColor": "#fff3bf", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#f59e0b", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "dHead_text", - "x": 291.45, - "y": 961.25, - "width": 177.10000000000002, - "height": 17.5, - "text": "Discovery (.well-known)", - "fontSize": 14, - "fontFamily": 1, - "containerId": "dHead", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 836560960, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 268053522, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 165355887, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 225359599, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "dLine", - "x": 380, - "y": 990, - "width": 0, - "height": 550, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 550 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 434646783, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 963980987, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "prHead_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "prHead", - "x": 550, - "y": 950, - "width": 160, - "height": 40, - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#22c55e", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "prHead_text", - "x": 556.85, - "y": 961.25, - "width": 146.3, - "height": 17.5, - "text": "TEA Product Release", - "fontSize": 14, - "fontFamily": 1, - "containerId": "prHead", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 532673844, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 416354069, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 546612311, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 12876854, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "prLine", - "x": 630, - "y": 990, - "width": 0, - "height": 550, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 550 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 986414963, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 60246222, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "crHead_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "crHead", - "x": 800, - "y": 950, - "width": 180, - "height": 40, - "backgroundColor": "#d0bfff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#8b5cf6", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "crHead_text", - "x": 809.15, - "y": 961.25, - "width": 161.70000000000002, - "height": 17.5, - "text": "TEA Component Release", - "fontSize": 14, - "fontFamily": 1, - "containerId": "crHead", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 678700252, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 159387056, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 628557341, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 604831108, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "crLine", - "x": 890, - "y": 990, - "width": 0, - "height": 550, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 550 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 30546574, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 135177463, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq1_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq1", - "x": 110, - "y": 1020, - "width": 270, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 270, - 0 - ] - ], - "strokeColor": "#f59e0b", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq1_text", - "x": 156.45, - "y": 1011.25, - "width": 177.10000000000002, - "height": 17.5, - "text": "1. GET /.well-known/tea", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq1", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 349482594, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 789890610, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 847461378, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 173622756, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq2_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq2", - "x": 380, - "y": 1060, - "width": -270, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -270, - 0 - ] - ], - "strokeColor": "#f59e0b", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq2_text", - "x": 152.6, - "y": 1051.25, - "width": 184.8, - "height": 17.5, - "text": "2. Returns API endpoints", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq2", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 110092748, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 31118657, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 255758566, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 900025800, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq3_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq3", - "x": 110, - "y": 1110, - "width": 270, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 270, - 0 - ] - ], - "strokeColor": "#f59e0b", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq3_text", - "x": 152.6, - "y": 1101.25, - "width": 184.8, - "height": 17.5, - "text": "3. Call Discovery w/ TEI", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq3", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 329326862, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 811770140, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 26311709, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 127687205, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq4_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq4", - "x": 380, - "y": 1150, - "width": -270, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -270, - 0 - ] - ], - "strokeColor": "#f59e0b", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq4_text", - "x": 137.2, - "y": 1141.25, - "width": 215.60000000000002, - "height": 17.5, - "text": "4. Return Product Release(s)", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq4", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 158861047, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 536938846, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 625818849, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 962926849, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq5_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq5", - "x": 110, - "y": 1200, - "width": 520, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 520, - 0 - ] - ], - "strokeColor": "#22c55e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq5_text", - "x": 281.45, - "y": 1191.25, - "width": 177.10000000000002, - "height": 17.5, - "text": "5. Resolve Prod Release", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq5", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 437731164, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 921428612, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 870790990, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 93929788, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq6_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq6", - "x": 630, - "y": 1240, - "width": -520, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -520, - 0 - ] - ], - "strokeColor": "#22c55e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq6_text", - "x": 262.2, - "y": 1231.25, - "width": 215.60000000000002, - "height": 17.5, - "text": "6. Return Component Releases", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq6", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 613806409, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 71366738, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 63362911, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 126503006, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq7_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq7", - "x": 110, - "y": 1290, - "width": 780, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 780, - 0 - ] - ], - "strokeColor": "#8b5cf6", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq7_text", - "x": 311.35, - "y": 1281.25, - "width": 377.3, - "height": 17.5, - "text": "7. For each Component Release, obtain collections", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq7", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 338008809, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 781177126, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 108508532, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 215234389, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "seq8_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "seq8", - "x": 890, - "y": 1330, - "width": -780, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -780, - 0 - ] - ], - "strokeColor": "#8b5cf6", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "seq8_text", - "x": 411.45, - "y": 1321.25, - "width": 177.10000000000002, - "height": 17.5, - "text": "8. Return TEA Artifacts", - "fontSize": 14, - "fontFamily": 1, - "containerId": "seq8", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 78058391, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 889122579, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - } - ], - "appState": { - "viewBackgroundColor": "#ffffff", - "currentItemStrokeColor": "#1e1e1e", - "currentItemBackgroundColor": "transparent", - "currentItemFillStyle": "solid", - "currentItemStrokeWidth": 2, - "currentItemStrokeStyle": "solid", - "currentItemRoughness": 1, - "currentItemOpacity": 100, - "currentItemFontFamily": 1, - "currentItemFontSize": 20, - "currentItemTextAlign": "left", - "currentItemStrokeSharpness": "round" - } -} \ No newline at end of file diff --git a/doc/tea_data_model.excalidraw b/doc/tea_data_model.excalidraw deleted file mode 100644 index bb60eae..0000000 --- a/doc/tea_data_model.excalidraw +++ /dev/null @@ -1,1015 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "version": 2, - "versionNonce": 167281445, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 866940117, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "text", - "id": "title", - "x": 300, - "y": 20, - "text": "Transparency Exchange API (TEA) Data Model", - "fontSize": 28, - "strokeColor": "#1e1e1e", - "width": 646.8000000000001, - "height": 35, - "baseline": 31, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "version": 2, - "versionNonce": 290557472, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 785009667, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "tei_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "tei", - "x": 50, - "y": 200, - "width": 220, - "height": 80, - "backgroundColor": "#fff3bf", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#f59e0b" - }, - { - "type": "text", - "id": "tei_text", - "x": 76.39999999999999, - "y": 220, - "width": 167.20000000000002, - "height": 40, - "text": "Discovery (TEI)\nResolves to Release", - "fontSize": 16, - "fontFamily": 1, - "containerId": "tei", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 238103836, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 276140534, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 36 - }, - { - "version": 2, - "versionNonce": 345925228, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 178738737, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "product_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "product", - "x": 400, - "y": 100, - "width": 200, - "height": 80, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#4a9eed" - }, - { - "type": "text", - "id": "product_text", - "x": 438.4, - "y": 120, - "width": 123.20000000000002, - "height": 40, - "text": "TEA Product\n(Product Line)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "product", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 318599718, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 576954118, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 36 - }, - { - "version": 2, - "versionNonce": 717272336, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 12733120, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "prod_rel_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "prod_rel", - "x": 400, - "y": 300, - "width": 200, - "height": 80, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#4a9eed" - }, - { - "type": "text", - "id": "prod_rel_text", - "x": 416.4, - "y": 320, - "width": 167.20000000000002, - "height": 40, - "text": "TEA Product Release\n(Primary Entry)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "prod_rel", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 559444216, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 645638008, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 36 - }, - { - "version": 2, - "versionNonce": 235904703, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 734388130, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_tei_strel_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_tei_strel", - "x": 270, - "y": 240, - "width": 130, - "height": 60, - "points": [ - [ - 0, - 0 - ], - [ - 130, - 60 - ] - ], - "strokeColor": "#f59e0b", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "tei", - "fixedPoint": [ - 1, - 0.5 - ] - }, - "endBinding": { - "elementId": "prod_rel", - "fixedPoint": [ - 0, - 0.5 - ] - }, - "startArrowhead": null - }, - { - "type": "text", - "id": "a_tei_strel_text", - "x": 292.65, - "y": 261.25, - "width": 84.7, - "height": 17.5, - "text": "resolves to", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_tei_strel", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 751043349, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 227857383, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 697299633, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 710202603, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_prod_rel_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_prod_rel", - "x": 500, - "y": 180, - "width": 0, - "height": 120, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 120 - ] - ], - "strokeColor": "#4a9eed", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "product", - "fixedPoint": [ - 0.5, - 1 - ] - }, - "endBinding": { - "elementId": "prod_rel", - "fixedPoint": [ - 0.5, - 0 - ] - }, - "startArrowhead": null - }, - { - "type": "text", - "id": "a_prod_rel_text", - "x": 453.8, - "y": 231.25, - "width": 92.4, - "height": 17.5, - "text": "has releases", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_prod_rel", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 122921120, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 554857509, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 232196529, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 449554058, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "component_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "component", - "x": 800, - "y": 100, - "width": 200, - "height": 80, - "backgroundColor": "#d0bfff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#8b5cf6" - }, - { - "type": "text", - "id": "component_text", - "x": 842.8, - "y": 120, - "width": 114.4, - "height": 40, - "text": "TEA Component\n(Lineage)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "component", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 674237119, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 422435724, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 36 - }, - { - "version": 2, - "versionNonce": 931136153, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 147374708, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "comp_rel_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "comp_rel", - "x": 800, - "y": 300, - "width": 240, - "height": 80, - "backgroundColor": "#d0bfff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#8b5cf6" - }, - { - "type": "text", - "id": "comp_rel_text", - "x": 812.2, - "y": 322.5, - "width": 215.60000000000002, - "height": 35, - "text": "TEA Component Release\n(/component/{uuid}/releases)", - "fontSize": 14, - "fontFamily": 1, - "containerId": "comp_rel", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 878891488, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 23290723, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 31 - }, - { - "version": 2, - "versionNonce": 370736448, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 401196270, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_comp_rel_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_comp_rel", - "x": 900, - "y": 180, - "width": 0, - "height": 120, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 120 - ] - ], - "strokeColor": "#8b5cf6", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "component", - "fixedPoint": [ - 0.5, - 1 - ] - }, - "endBinding": { - "elementId": "comp_rel", - "fixedPoint": [ - 0.5, - 0 - ] - }, - "startArrowhead": null - }, - { - "type": "text", - "id": "a_comp_rel_text", - "x": 853.8, - "y": 231.25, - "width": 92.4, - "height": 17.5, - "text": "has releases", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_comp_rel", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 598949984, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 312229238, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 113491510, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 429136782, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "collection_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "collection", - "x": 600, - "y": 500, - "width": 220, - "height": 80, - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#22c55e" - }, - { - "type": "text", - "id": "collection_text", - "x": 639.6, - "y": 520, - "width": 140.8, - "height": 40, - "text": "TEA Collection\n(Versioned list)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "collection", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 584982859, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 587906769, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 36 - }, - { - "version": 2, - "versionNonce": 499529419, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 761705895, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_prel_coll_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_prel_coll", - "x": 500, - "y": 380, - "width": 100, - "height": 160, - "points": [ - [ - 0, - 0 - ], - [ - 100, - 160 - ] - ], - "strokeColor": "#1e1e1e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "prod_rel", - "fixedPoint": [ - 0.5, - 1 - ] - }, - "endBinding": { - "elementId": "collection", - "fixedPoint": [ - 0, - 0.5 - ] - }, - "startArrowhead": null - }, - { - "type": "text", - "id": "a_prel_coll_text", - "x": 538.45, - "y": 451.25, - "width": 23.1, - "height": 17.5, - "text": "has", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_prel_coll", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 338494172, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 166154473, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 835357745, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 947892553, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_crel_coll_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_crel_coll", - "x": 900, - "y": 380, - "width": -80, - "height": 160, - "points": [ - [ - 0, - 0 - ], - [ - -80, - 160 - ] - ], - "strokeColor": "#1e1e1e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "comp_rel", - "fixedPoint": [ - 0.5, - 1 - ] - }, - "endBinding": { - "elementId": "collection", - "fixedPoint": [ - 1, - 0.5 - ] - }, - "startArrowhead": null - }, - { - "type": "text", - "id": "a_crel_coll_text", - "x": 848.45, - "y": 451.25, - "width": 23.1, - "height": 17.5, - "text": "has", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_crel_coll", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 625958386, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 497177979, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 441589794, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 464410241, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "artifact_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "rectangle", - "id": "artifact", - "x": 600, - "y": 700, - "width": 220, - "height": 80, - "backgroundColor": "#eebefa", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#ec4899" - }, - { - "type": "text", - "id": "artifact_text", - "x": 613.2, - "y": 720, - "width": 193.60000000000002, - "height": 40, - "text": "TEA Artifact\n(xBOM, VEX, CDXA, CLE)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "artifact", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 3658297, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 886159966, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 36 - }, - { - "version": 2, - "versionNonce": 64546817, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 412898613, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_coll_art_text", - "type": "text" - } - ], - "updated": 1771600797567, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_coll_art", - "x": 710, - "y": 580, - "width": 0, - "height": 120, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 120 - ] - ], - "strokeColor": "#22c55e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "collection", - "fixedPoint": [ - 0.5, - 1 - ] - }, - "endBinding": { - "elementId": "artifact", - "fixedPoint": [ - 0.5, - 0 - ] - }, - "startArrowhead": null - }, - { - "type": "text", - "id": "a_coll_art_text", - "x": 679.2, - "y": 631.25, - "width": 61.60000000000001, - "height": 17.5, - "text": "contains", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_coll_art", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 107192323, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 817296938, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797567, - "link": null, - "locked": false, - "baseline": 13.5 - } - ], - "appState": { - "viewBackgroundColor": "#ffffff", - "currentItemStrokeColor": "#1e1e1e", - "currentItemBackgroundColor": "transparent", - "currentItemFillStyle": "solid", - "currentItemStrokeWidth": 2, - "currentItemStrokeStyle": "solid", - "currentItemRoughness": 1, - "currentItemOpacity": 100, - "currentItemFontFamily": 1, - "currentItemFontSize": 20, - "currentItemTextAlign": "left", - "currentItemStrokeSharpness": "round" - } -} \ No newline at end of file diff --git a/doc/tea_discovery_security.excalidraw b/doc/tea_discovery_security.excalidraw deleted file mode 100644 index c45f23b..0000000 --- a/doc/tea_discovery_security.excalidraw +++ /dev/null @@ -1,1103 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", - "elements": [ - { - "version": 3, - "versionNonce": 835041064, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 737075209, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "text", - "id": "t_disc_title", - "x": 300, - "y": 20, - "text": "TEA Discovery & Security Architecture", - "fontSize": 28, - "strokeColor": "#1e1e1e", - "width": 569.8000000000001, - "height": 35, - "baseline": 31, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "index": "a0", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": { - "type": 2 - }, - "containerId": null, - "originalText": "TEA Discovery & Security Architecture", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 149, - "versionNonce": 786606632, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 105464567, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "tei_struct_text", - "type": "text" - } - ], - "updated": 1771601605550, - "link": null, - "locked": false, - "type": "rectangle", - "id": "tei_struct", - "x": 49.9765625, - "y": 100, - "width": 450.0234375, - "height": 300.3515625, - "backgroundColor": "#fff3bf", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#f59e0b", - "index": "a1", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "tei_struct_text", - "x": 94.70040893554688, - "y": 230.17578125, - "width": 360.57574462890625, - "height": 40, - "text": "Transparency Exchange Identifier (TEI)\nurn:tei:::", - "fontSize": 16, - "fontFamily": 1, - "containerId": "tei_struct", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 150, - "versionNonce": 73082152, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 744852632, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601605550, - "link": null, - "locked": false, - "baseline": 36, - "index": "a2", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "Transparency Exchange Identifier (TEI)\nurn:tei:::", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 376, - "versionNonce": 896843096, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 819988572, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601611430, - "link": null, - "locked": false, - "type": "text", - "id": "t_types", - "x": 62.16796875, - "y": 139.89453125, - "text": "Supported Types: PURL, SWID, HASH,\nUUID, EAN/UPC, GTIN, ASIN, UDI", - "fontSize": 22.564062499999995, - "strokeColor": "#1e1e1e", - "width": 421.94796875000003, - "height": 56.41015625, - "baseline": 36, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "index": "a3", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": { - "type": 2 - }, - "containerId": null, - "originalText": "Supported Types: PURL, SWID, HASH,\nUUID, EAN/UPC, GTIN, ASIN, UDI", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 810552616, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 707153589, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "dns_res_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "rectangle", - "id": "dns_res", - "x": 650, - "y": 100, - "width": 200, - "height": 80, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#4a9eed", - "index": "a4", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "dns_res_text", - "x": 670.8, - "y": 120, - "width": 158.4, - "height": 40, - "text": "DNS Resolution\n(from domain-name)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "dns_res", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 1180744024, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 225575650, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 36, - "index": "a5", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "DNS Resolution\n(from domain-name)", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 1478764584, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 793267654, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_tei_dns", - "x": 500, - "y": 140, - "width": 150, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 150, - 0 - ] - ], - "strokeColor": "#f59e0b", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "tei_struct", - "fixedPoint": [ - 1, - 0.5 - ], - "focus": 0 - }, - "endBinding": { - "elementId": "dns_res", - "fixedPoint": [ - 0, - 0.5 - ], - "focus": 0 - }, - "startArrowhead": null, - "lastCommittedPoint": null, - "index": "a6", - "fillStyle": "solid", - "backgroundColor": "transparent", - "roundness": { - "type": 2 - } - }, - { - "version": 3, - "versionNonce": 1236258392, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 277235211, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "well_known_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "rectangle", - "id": "well_known", - "x": 600, - "y": 300, - "width": 300, - "height": 100, - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#22c55e", - "index": "a7", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "well_known_text", - "x": 609.2, - "y": 330, - "width": 281.6, - "height": 40, - "text": "Discovery Endpoint\nhttps:///.well-known/tea", - "fontSize": 16, - "fontFamily": 1, - "containerId": "well_known", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 1153229608, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 130605028, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 36, - "index": "a8", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "Discovery Endpoint\nhttps:///.well-known/tea", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 475284312, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 966250622, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_dns_wk_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_dns_wk", - "x": 750, - "y": 180, - "width": 0, - "height": 120, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 120 - ] - ], - "strokeColor": "#4a9eed", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "dns_res", - "fixedPoint": [ - 0.5, - 1 - ], - "focus": 0 - }, - "endBinding": { - "elementId": "well_known", - "fixedPoint": [ - 0.5, - 0 - ], - "focus": 0 - }, - "startArrowhead": null, - "lastCommittedPoint": null, - "index": "a9", - "fillStyle": "solid", - "backgroundColor": "transparent", - "roundness": { - "type": 2 - } - }, - { - "type": "text", - "id": "a_dns_wk_text", - "x": 715.35, - "y": 231.25, - "width": 69.30000000000001, - "height": 17.5, - "text": "HTTPS GET", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_dns_wk", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 881212968, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 840751313, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 13.5, - "index": "aA", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "HTTPS GET", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 1822562392, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 916185838, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "json_resp_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "rectangle", - "id": "json_resp", - "x": 600, - "y": 500, - "width": 300, - "height": 100, - "backgroundColor": "#d0bfff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#8b5cf6", - "index": "aB", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "json_resp_text", - "x": 626.8, - "y": 530, - "width": 246.40000000000003, - "height": 40, - "text": "TEA Server Index (JSON)\nList of endpoints & versions", - "fontSize": 16, - "fontFamily": 1, - "containerId": "json_resp", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 946616616, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 780073158, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 36, - "index": "aC", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "TEA Server Index (JSON)\nList of endpoints & versions", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 2121092440, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 223506811, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_wk_json_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_wk_json", - "x": 750, - "y": 400, - "width": 0, - "height": 100, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 100 - ] - ], - "strokeColor": "#22c55e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "well_known", - "fixedPoint": [ - 0.5, - 1 - ], - "focus": 0 - }, - "endBinding": { - "elementId": "json_resp", - "fixedPoint": [ - 0.5, - 0 - ], - "focus": 0 - }, - "startArrowhead": null, - "lastCommittedPoint": null, - "index": "aD", - "fillStyle": "solid", - "backgroundColor": "transparent", - "roundness": { - "type": 2 - } - }, - { - "type": "text", - "id": "a_wk_json_text", - "x": 669.15, - "y": 441.25, - "width": 161.70000000000002, - "height": 17.5, - "text": "Returns endpoint URLs", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_wk_json", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 1675285544, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 914463783, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 13.5, - "index": "aE", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "Returns endpoint URLs", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 2064245336, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 70566273, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "client_logic_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "rectangle", - "id": "client_logic", - "x": 100, - "y": 500, - "width": 300, - "height": 100, - "backgroundColor": "#ffc9c9", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#ef4444", - "index": "aF", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "client_logic_text", - "x": 104.79999999999998, - "y": 520, - "width": 290.40000000000003, - "height": 60, - "text": "Client Selection Logic\nMatches version, honors priority,\nretries on failure (5xx, cert)", - "fontSize": 16, - "fontFamily": 1, - "containerId": "client_logic", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 2092830504, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 121030079, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 56, - "index": "aG", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "Client Selection Logic\nMatches version, honors priority,\nretries on failure (5xx, cert)", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 1095489368, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 827435008, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_json_cli", - "x": 600, - "y": 550, - "width": 200, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -200, - 0 - ] - ], - "strokeColor": "#8b5cf6", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "json_resp", - "fixedPoint": [ - 0, - 0.5 - ], - "focus": 0 - }, - "endBinding": { - "elementId": "client_logic", - "fixedPoint": [ - 1, - 0.5 - ], - "focus": 0 - }, - "startArrowhead": null, - "lastCommittedPoint": null, - "index": "aH", - "fillStyle": "solid", - "backgroundColor": "transparent", - "roundness": { - "type": 2 - } - }, - { - "version": 3, - "versionNonce": 729934376, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 612330223, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "full_api_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "rectangle", - "id": "full_api", - "x": 100, - "y": 700, - "width": 350, - "height": 80, - "backgroundColor": "#eebefa", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#ec4899", - "index": "aI", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "full_api_text", - "x": 90.19999999999999, - "y": 722.5, - "width": 369.6, - "height": 35, - "text": "Constructed API Call\n/v/discovery?tei=", - "fontSize": 14, - "fontFamily": 1, - "containerId": "full_api", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 898051160, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 897653891, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 31, - "index": "aJ", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "Constructed API Call\n/v/discovery?tei=", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 722625832, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 863461482, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_cli_api", - "x": 250, - "y": 600, - "width": 0, - "height": 100, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 100 - ] - ], - "strokeColor": "#ef4444", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "client_logic", - "fixedPoint": [ - 0.5, - 1 - ], - "focus": 0 - }, - "endBinding": { - "elementId": "full_api", - "fixedPoint": [ - 0.5, - 0 - ], - "focus": 0 - }, - "startArrowhead": null, - "lastCommittedPoint": null, - "index": "aK", - "fillStyle": "solid", - "backgroundColor": "transparent", - "roundness": { - "type": 2 - } - }, - { - "version": 3, - "versionNonce": 294642008, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 935183848, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "auth_layer_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "rectangle", - "id": "auth_layer", - "x": 600, - "y": 700, - "width": 250, - "height": 100, - "backgroundColor": "#c3fae8", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#15803d", - "index": "aL", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "auth_layer_text", - "x": 601.8, - "y": 723.75, - "width": 246.40000000000003, - "height": 52.5, - "text": "Security / Auth Layer\n- HTTP Bearer Token\n- Mutual TLS (mTLS) Client Certs", - "fontSize": 14, - "fontFamily": 1, - "containerId": "auth_layer", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 2027651112, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 672485143, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 48.5, - "index": "aM", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "Security / Auth Layer\n- HTTP Bearer Token\n- Mutual TLS (mTLS) Client Certs", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "version": 3, - "versionNonce": 884602456, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 240663246, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "a_api_auth_text", - "type": "text" - } - ], - "updated": 1771601377884, - "link": null, - "locked": false, - "type": "arrow", - "id": "a_api_auth", - "x": 450, - "y": 740, - "width": 150, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 150, - 0 - ] - ], - "strokeColor": "#ec4899", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startBinding": { - "elementId": "full_api", - "fixedPoint": [ - 1, - 0.5 - ], - "focus": 0 - }, - "endBinding": { - "elementId": "auth_layer", - "fixedPoint": [ - 0, - 0.5 - ], - "focus": 0 - }, - "startArrowhead": null, - "lastCommittedPoint": null, - "index": "aN", - "fillStyle": "solid", - "backgroundColor": "transparent", - "roundness": { - "type": 2 - } - }, - { - "type": "text", - "id": "a_api_auth_text", - "x": 482.65, - "y": 731.25, - "width": 84.7, - "height": 17.5, - "text": "secured via", - "fontSize": 14, - "fontFamily": 1, - "containerId": "a_api_auth", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 3, - "versionNonce": 80296744, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 317220990, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771601377884, - "link": null, - "locked": false, - "baseline": 13.5, - "index": "aO", - "fillStyle": "solid", - "strokeWidth": 2, - "backgroundColor": "transparent", - "roundness": null, - "originalText": "secured via", - "autoResize": true, - "lineHeight": 1.25 - } - ], - "appState": { - "gridSize": 20, - "gridStep": 5, - "gridModeEnabled": false, - "viewBackgroundColor": "#ffffff" - }, - "files": {} -} \ No newline at end of file diff --git a/doc/tea_publisher_flow.excalidraw b/doc/tea_publisher_flow.excalidraw deleted file mode 100644 index b34cc6d..0000000 --- a/doc/tea_publisher_flow.excalidraw +++ /dev/null @@ -1,948 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "version": 2, - "versionNonce": 735189831, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 493599073, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "text", - "id": "t_seq2", - "x": 350, - "y": 1650, - "text": "TEA Publisher Flow", - "fontSize": 28, - "strokeColor": "#1e1e1e", - "width": 277.20000000000005, - "height": 35, - "baseline": 31, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "version": 2, - "versionNonce": 983350661, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 372839194, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pbHead_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "pbHead", - "x": 50, - "y": 1750, - "width": 120, - "height": 40, - "backgroundColor": "#ffc9c9", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#ef4444", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "pbHead_text", - "x": 83.6, - "y": 1760, - "width": 52.800000000000004, - "height": 20, - "text": "Vendor", - "fontSize": 16, - "fontFamily": 1, - "containerId": "pbHead", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 891424686, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 519508987, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 16 - }, - { - "version": 2, - "versionNonce": 200057426, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 160900905, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pbLine", - "x": 110, - "y": 1790, - "width": 0, - "height": 350, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 350 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 890327808, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 961803583, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "ellipse", - "id": "pbh", - "x": 100, - "y": 1700, - "width": 20, - "height": 20, - "backgroundColor": "#ffc9c9", - "fillStyle": "solid", - "strokeColor": "#ef4444", - "strokeWidth": 2 - }, - { - "version": 2, - "versionNonce": 417291581, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 223982292, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "pbb", - "x": 100, - "y": 1722, - "width": 20, - "height": 20, - "backgroundColor": "#ffc9c9", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#ef4444", - "strokeWidth": 2 - }, - { - "version": 2, - "versionNonce": 171505583, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 206442783, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "ppHead_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "ppHead", - "x": 300, - "y": 1750, - "width": 160, - "height": 40, - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#4a9eed", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "ppHead_text", - "x": 337.65, - "y": 1761.25, - "width": 84.7, - "height": 17.5, - "text": "TEA Product", - "fontSize": 14, - "fontFamily": 1, - "containerId": "ppHead", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 111956121, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 540702299, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 608506924, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 995563976, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "ppLine", - "x": 380, - "y": 1790, - "width": 0, - "height": 350, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 350 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 359271078, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 459983326, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pcHead_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "pcHead", - "x": 550, - "y": 1750, - "width": 160, - "height": 40, - "backgroundColor": "#d0bfff", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#8b5cf6", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "pcHead_text", - "x": 579.95, - "y": 1761.25, - "width": 100.10000000000001, - "height": 17.5, - "text": "TEA Component", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pcHead", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 339317497, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 266302514, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 43997061, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 66353842, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pcLine", - "x": 630, - "y": 1790, - "width": 0, - "height": 350, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 350 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 10162484, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 467471766, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pcHead2_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "rectangle", - "id": "pcHead2", - "x": 800, - "y": 1750, - "width": 180, - "height": 40, - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "roundness": { - "type": 3 - }, - "strokeColor": "#22c55e", - "strokeWidth": 2 - }, - { - "type": "text", - "id": "pcHead2_text", - "x": 836.1, - "y": 1761.25, - "width": 107.80000000000001, - "height": 17.5, - "text": "TEA Collection", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pcHead2", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 34816800, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 367252007, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 746602882, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 803867599, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pcLine2", - "x": 890, - "y": 1790, - "width": 0, - "height": 350, - "points": [ - [ - 0, - 0 - ], - [ - 0, - 350 - ] - ], - "strokeColor": "#b0b0b0", - "strokeWidth": 1, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "version": 2, - "versionNonce": 479137662, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 8343542, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pseq1_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pseq1", - "x": 110, - "y": 1840, - "width": 270, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 270, - 0 - ] - ], - "strokeColor": "#4a9eed", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "pseq1_text", - "x": 171.85, - "y": 1831.25, - "width": 146.3, - "height": 17.5, - "text": "1. POST /v1/product", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pseq1", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 919186660, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 985070238, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 285675331, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 878566305, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pseq2_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pseq2", - "x": 380, - "y": 1880, - "width": -270, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -270, - 0 - ] - ], - "strokeColor": "#4a9eed", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "pseq2_text", - "x": 114.1, - "y": 1871.25, - "width": 261.8, - "height": 17.5, - "text": "2. Returns Product Identifier (PI)", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pseq2", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 365632409, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 430366614, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 500453078, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 484489509, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pseq3_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pseq3", - "x": 110, - "y": 1930, - "width": 520, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 520, - 0 - ] - ], - "strokeColor": "#8b5cf6", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "pseq3_text", - "x": 177.49999999999997, - "y": 1921.25, - "width": 385.00000000000006, - "height": 17.5, - "text": "3. POST /v1/component (with PI, Component Version)", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pseq3", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 996674539, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 149908619, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 732777045, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 375918744, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pseq4_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pseq4", - "x": 630, - "y": 1970, - "width": -520, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -520, - 0 - ] - ], - "strokeColor": "#8b5cf6", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "pseq4_text", - "x": 281.45, - "y": 1961.25, - "width": 177.10000000000002, - "height": 17.5, - "text": "4. Returns Component ID", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pseq4", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 290342518, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 985361675, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 86711719, - "isDeleted": false, - "strokeStyle": "solid", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 585995016, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pseq5_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pseq5", - "x": 110, - "y": 2020, - "width": 780, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - 780, - 0 - ] - ], - "strokeColor": "#22c55e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "pseq5_text", - "x": 288.25, - "y": 2011.25, - "width": 423.50000000000006, - "height": 17.5, - "text": "5. POST /v1/collection (with Component ID and Artifact)", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pseq5", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 322237589, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 645475552, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - }, - { - "version": 2, - "versionNonce": 718048721, - "isDeleted": false, - "strokeStyle": "dashed", - "strokeSharpness": "round", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 935023425, - "groupIds": [], - "frameId": null, - "boundElements": [ - { - "id": "pseq6_text", - "type": "text" - } - ], - "updated": 1771600797571, - "link": null, - "locked": false, - "type": "arrow", - "id": "pseq6", - "x": 890, - "y": 2060, - "width": -780, - "height": 0, - "points": [ - [ - 0, - 0 - ], - [ - -780, - 0 - ] - ], - "strokeColor": "#22c55e", - "strokeWidth": 2, - "endArrowhead": "arrow", - "startArrowhead": null - }, - { - "type": "text", - "id": "pseq6_text", - "x": 407.6, - "y": 2051.25, - "width": 184.8, - "height": 17.5, - "text": "6. Returns Collection ID", - "fontSize": 14, - "fontFamily": 1, - "containerId": "pseq6", - "verticalAlign": "middle", - "textAlign": "center", - "strokeColor": "#1e1e1e", - "version": 2, - "versionNonce": 288204797, - "isDeleted": false, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 464034892, - "groupIds": [], - "frameId": null, - "boundElements": [], - "updated": 1771600797571, - "link": null, - "locked": false, - "baseline": 13.5 - } - ], - "appState": { - "viewBackgroundColor": "#ffffff", - "currentItemStrokeColor": "#1e1e1e", - "currentItemBackgroundColor": "transparent", - "currentItemFillStyle": "solid", - "currentItemStrokeWidth": 2, - "currentItemStrokeStyle": "solid", - "currentItemRoughness": 1, - "currentItemOpacity": 100, - "currentItemFontFamily": 1, - "currentItemFontSize": 20, - "currentItemTextAlign": "left", - "currentItemStrokeSharpness": "round" - } -} \ No newline at end of file diff --git a/doc/versioning.md b/doc/versioning.md deleted file mode 100644 index f91330c..0000000 --- a/doc/versioning.md +++ /dev/null @@ -1,146 +0,0 @@ -# Versioning - -The Transparency Exchange API (TEA) uses semantic versioning for API evolution. This document describes the versioning strategy, compatibility guarantees, and migration guidelines. - -## Semantic Versioning - -TEA follows [Semantic Versioning 2.0.0](https://semver.org/) for API versions: - -``` -MAJOR.MINOR.PATCH -``` - -### Version Components - -- **MAJOR**: Breaking changes that require client updates -- **MINOR**: Backward-compatible additions (new endpoints, optional fields) -- **PATCH**: Backward-compatible bug fixes - -### Pre-release Versions - -Pre-release versions use the format: - -``` -MAJOR.MINOR.PATCH-PRERELEASE -``` - -Examples: - -- `1.0.0-alpha.1` -- `1.0.0-beta.2` -- `1.0.0-rc.1` - -## API Versioning Strategy - -### URL Versioning - -API versions are included in the URL path: - -``` -/v{MAJOR}/... -``` - -Current version: `v1` - -### Content Negotiation - -For content that may evolve independently of the API version, use content negotiation with the `Accept` header: - -``` -Accept: application/vnd.cyclonedx+json; version=1.5 -``` - -## Compatibility Guarantees - -### Backward Compatibility - -- **PATCH** versions: Fully backward compatible -- **MINOR** versions: Backward compatible additions only -- **MAJOR** versions: May include breaking changes - -### Forward Compatibility - -Clients SHOULD ignore unknown fields in responses. Servers MUST NOT require unknown fields in requests. - -### Deprecation Policy - -1. Features are marked as deprecated in MINOR releases -2. Deprecated features are removed in the next MAJOR release -3. Deprecation notices include: - - Deprecation version - - Removal version - - Migration guidance - -## Version Discovery - -### Well-Known Endpoint - -Clients discover available API versions through `/.well-known/tea`: - -```json -{ - "schemaVersion": 1, - "endpoints": [ - { - "url": "https://api.example.com/tea/v1", - "versions": ["1.0.0", "1.1.0"], - "priority": 1 - } - ] -} -``` - -### Version Headers - -Servers MAY include version information in responses: - -``` -X-API-Version: 1.0.0 -``` - -## Migration Guidelines - -### Minor Version Upgrades - -1. Review release notes for new features -2. Update client code to handle new optional fields -3. Test with new version in staging environment -4. Gradually roll out updated clients - -### Major Version Upgrades - -1. Review breaking changes documentation -2. Update client code for required changes -3. Implement feature flags if needed -4. Test extensively in staging -5. Plan rollback strategy -6. Execute blue-green deployment - -### Testing Strategy - -- Maintain test suites for multiple API versions during transition periods -- Use contract testing to validate compatibility -- Implement canary deployments for gradual rollout - -## Implementation Considerations - -### Server-Side - -- Support multiple concurrent API versions -- Use version-aware routing -- Implement graceful degradation for older clients -- Provide version-specific documentation - -### Client-Side - -- Implement version negotiation logic -- Handle version-specific response formats -- Provide upgrade prompts for deprecated versions -- Support fallback to older versions when possible - -## Version Support Policy - -- Current MAJOR version receives active development and support -- Previous MAJOR version receives security updates only -- Versions older than N-1 MAJOR releases are deprecated -- Deprecation notices provided 6 months before end of support diff --git a/graph-schema.json b/graph-schema.json deleted file mode 100644 index ac32c88..0000000 --- a/graph-schema.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Dependency Graph Schema", - "description": "Minimal JSON schema for dependency graph with nodes and edges.", - "type": "object", - "properties": { - "nodes": { - "type": "array", - "description": "List of dependency nodes.", - "items": { - "type": "object", - "properties": { - "purl": { - "type": "string", - "description": "Package URL (PURL) identifier." - }, - "name": { - "type": "string", - "description": "Package name." - }, - "version": { - "type": "string", - "description": "Exact version." - }, - "digest": { - "type": "string", - "description": "Content digest (e.g., SHA256)." - }, - "license": { - "type": "string", - "description": "License SPDX identifier." - }, - "source_repo": { - "type": "string", - "description": "Source repository URL." - }, - "lifecycle_state": { - "enum": ["ACTIVE", "DEPRECATED", "QUARANTINED", "RETIRED"], - "description": "Lifecycle state for deprecation protocol." - } - }, - "required": ["purl", "name", "version", "digest", "lifecycle_state"] - } - }, - "edges": { - "type": "array", - "description": "List of dependency edges.", - "items": { - "type": "object", - "properties": { - "from_purl": { - "type": "string", - "description": "PURL of the dependent package." - }, - "to_purl": { - "type": "string", - "description": "PURL of the dependency." - }, - "scope": { - "enum": ["build", "runtime"], - "description": "Scope of the dependency." - }, - "reason": { - "enum": ["direct", "transitive"], - "description": "Direct or transitive dependency." - } - }, - "required": ["from_purl", "to_purl", "scope", "reason"] - } - } - }, - "required": ["nodes", "edges"] -} diff --git a/proto/tea/v1/artifact.proto b/proto/tea/v1/artifact.proto deleted file mode 100644 index 5028174..0000000 --- a/proto/tea/v1/artifact.proto +++ /dev/null @@ -1,364 +0,0 @@ -// 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 deleted file mode 100644 index 0facc2a..0000000 --- a/proto/tea/v1/collection.proto +++ /dev/null @@ -1,316 +0,0 @@ -// 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 deleted file mode 100644 index 57e9cbe..0000000 --- a/proto/tea/v1/common.proto +++ /dev/null @@ -1,371 +0,0 @@ -// 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 deleted file mode 100644 index 61c053f..0000000 --- a/proto/tea/v1/component.proto +++ /dev/null @@ -1,334 +0,0 @@ -// 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 deleted file mode 100644 index 1178f82..0000000 --- a/proto/tea/v1/consumer.proto +++ /dev/null @@ -1,386 +0,0 @@ -// 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 deleted file mode 100644 index 97d9c3f..0000000 --- a/proto/tea/v1/discovery.proto +++ /dev/null @@ -1,360 +0,0 @@ -// 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 deleted file mode 100644 index 25840d8..0000000 --- a/proto/tea/v1/product.proto +++ /dev/null @@ -1,302 +0,0 @@ -// 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 deleted file mode 100644 index c676f0d..0000000 --- a/schemas/json/artifact/artifact-format.schema.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$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 deleted file mode 100644 index 29ba648..0000000 --- a/schemas/json/artifact/artifact.schema.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "$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 deleted file mode 100644 index 7b194ec..0000000 --- a/schemas/json/collection/collection.schema.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$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 deleted file mode 100644 index 25db210..0000000 --- a/schemas/json/collection/update-reason.schema.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$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 deleted file mode 100644 index 1382c4d..0000000 --- a/schemas/json/common/checksum.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$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 deleted file mode 100644 index 2615c65..0000000 --- a/schemas/json/common/error.schema.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "$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 deleted file mode 100644 index b20165b..0000000 --- a/schemas/json/common/identifier.schema.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$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 deleted file mode 100644 index 4acbd8a..0000000 --- a/schemas/json/common/pagination.schema.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$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 deleted file mode 100644 index 97b0e85..0000000 --- a/schemas/json/component/component-release.schema.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$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 deleted file mode 100644 index 66df2ae..0000000 --- a/schemas/json/component/component.schema.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$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 deleted file mode 100644 index 2cf1294..0000000 --- a/schemas/json/component/distribution.schema.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$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 deleted file mode 100644 index 46c0074..0000000 --- a/schemas/json/component/license.schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$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 deleted file mode 100644 index eab8cc9..0000000 --- a/schemas/json/discovery/discovery-response.schema.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "$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 deleted file mode 100644 index 3870ed8..0000000 --- a/schemas/json/product/component-ref.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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 deleted file mode 100644 index d38caf8..0000000 --- a/schemas/json/product/contact.schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$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 deleted file mode 100644 index 33a9dea..0000000 --- a/schemas/json/product/product-release.schema.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "$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 deleted file mode 100644 index 040bd77..0000000 --- a/schemas/json/product/product.schema.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$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 deleted file mode 100644 index 64f5547..0000000 --- a/schemas/json/product/vendor.schema.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$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