Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions docs/db-schema.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Database Schema

> Auto-generated from H2 Flyway migrations. Do not edit manually.
> Generated at: 2026-03-05T11:28:12.107568Z
> Generated at: 2026-03-06T11:16:25.615748Z

## Tables

- [ADAPTER_DEPLOYMENT](#adapter_deployment)
- [ADAPTER_IMAGE_DEFINITION](#adapter_image_definition)
- [COMPONENT_REMOVAL](#component_removal)
- [DEPLOYMENT](#deployment)
- [DEPLOYMENT_TOPICS](#deployment_topics)
- [DISPOSABLE_RESOURCE](#disposable_resource)
- [DOMAIN_WHITELIST](#domain_whitelist)
- [IMAGE_DEFINITION](#image_definition)
Expand Down Expand Up @@ -49,9 +50,6 @@
| DISPLAY_NAME | VARCHAR(1000000000) | No | | |
| DESCRIPTION | VARCHAR(1000000000) | Yes | | |
| ENVS | JSON | Yes | | |
| INITIAL_SCALE | INTEGER | Yes | | |
| MIN_SCALE | INTEGER | Yes | | |
| MAX_SCALE | INTEGER | Yes | | |
| STATUS | VARCHAR(32) | Yes | | |
| URL | VARCHAR(2048) | Yes | | |
| RESOURCES | JSON | Yes | | |
Expand All @@ -65,11 +63,23 @@
| ALLOWED_DOMAINS | JSON | No | JSON '[]' | |
| PROBE_PROPERTIES | JSON | Yes | | |
| IMAGE_DEFINITION_TYPE | VARCHAR(20) | Yes | | |
| SCALING | JSON | Yes | | |

**Indexes:**

- `IDX_DEPLOYMENT_IMAGE_DEFINITION_ID` on (IMAGE_DEFINITION_ID)

## DEPLOYMENT_TOPICS

| Column | Type | Nullable | Default | Key |
|--------|------|----------|---------|-----|
| DEPLOYMENT_ID | VARCHAR(36) | No | | PK, FK → DEPLOYMENT.ID |
| TOPIC_NAME | VARCHAR(255) | No | | PK |

**Indexes:**

- `FK_DEPLOYMENT_TOPICS_DEPLOYMENT_INDEX_B` on (DEPLOYMENT_ID)

## DISPOSABLE_RESOURCE

| Column | Type | Nullable | Default | Key |
Expand Down Expand Up @@ -138,7 +148,6 @@
| MODEL_FORMAT | VARCHAR(32) | No | | |
| SOURCE | JSON | No | | |
| COMMAND | JSON | Yes | | |
| SCALING | JSON | Yes | | |

## INTERCEPTOR_DEPLOYMENT

Expand Down
41 changes: 41 additions & 0 deletions docs/rest-collection/dm_postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,47 @@
},
"response": []
},
{
"name": "call tool",
"protocolProfileBehavior": {
"followRedirects": true,
"disableUrlEncoding": false,
"disableCookies": false
},
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"tool_1\",\n \"arguments\": {\"arg1\": \"val1\"}\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{HOST}}/api/v1/deployments/mcp/{{DEPLOYMENT_NAME}}/call-tool",
"host": [
"{{HOST}}"
],
"path": [
"api",
"v1",
"deployments",
"mcp",
"{{DEPLOYMENT_NAME}}",
"call-tool"
]
}
},
"response": []
},
{
"name": "get prompts",
"protocolProfileBehavior": {
Expand Down
35 changes: 35 additions & 0 deletions specs/001-deployment-topics/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Specification Quality Checklist: Deployment Topics

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-05
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- The Assumptions section references specific code artifacts (`@ValidTopics`, `TopicRepository`, table names) — this is intentional context for the planning phase, not implementation specification. The core requirements and success criteria remain technology-agnostic.
- All items pass. Spec is ready for `/speckit.clarify` or `/speckit.plan`.
229 changes: 229 additions & 0 deletions specs/001-deployment-topics/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# Implementation Plan: Deployment Topics

**Branch**: `001-deployment-topics` | **Date**: 2026-03-05 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/001-deployment-topics/spec.md`

## Summary

Add an optional `topics` field (list of free-form string labels) to deployments, mirroring the existing implementation on image definitions. This involves: a new `deployment_topics` join table, model/DTO/entity additions across all layers, validation reuse (`@ValidTopics`), updating the global topics listing query, and ensuring duplication + export/import propagate topics.

## Technical Context

**Language/Version**: Java 21, Spring Boot 3.5.10, Gradle 8.13
**Primary Dependencies**: Hibernate/JPA (via Spring Data), MapStruct 1.6.0, Flyway 11.14.0, Lombok, Jackson 2.21.1
**Storage**: PostgreSQL, SQL Server, H2 (multi-vendor via Flyway)
**Testing**: JUnit 5, Testcontainers 1.21.3, AssertJ, H2/Postgres/SQL Server functional tests
**Target Platform**: Linux server (K8s)
**Project Type**: Web service (Spring Boot REST API)
**Performance Goals**: N/A (additive field; no new latency-sensitive paths)
**Constraints**: Checkstyle 10.21.4 (Google Java Style, 180 char line limit, `-Werror`)
**Scale/Scope**: Small feature — ~15 files modified, 3 new migration files, 0 new Java classes

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Status | Notes |
|-----------|--------|-------|
| I. Strict Layered Architecture | PASS | Topics field added at each layer following existing pattern |
| II. Transactional Discipline | PASS | No new `@Transactional` — existing service methods handle persistence |
| III. Kubernetes Isolation | PASS | No K8s API involvement |
| IV. Observability First | PASS | No new components; existing `@LogExecution` components unchanged |
| V. Security by Configuration | PASS | Topics are not sensitive data |
| Code Style | PASS | All changes follow Google Java Style |
| Multi-Vendor Database | PASS | Migration in all 3 vendor directories |
| Anti-Patterns | PASS | No anti-patterns introduced |

## Project Structure

### Documentation (this feature)

```text
specs/001-deployment-topics/
├── spec.md
├── plan.md
├── checklists/
│ └── requirements.md
└── tasks.md
```

### Source Code (files to modify)

```text
src/main/
├── java/com/epam/aidial/deployment/manager/
│ ├── model/deployment/
│ │ ├── Deployment.java # Add topics field
│ │ └── CreateDeployment.java # Add topics field
│ ├── dao/
│ │ ├── entity/deployment/
│ │ │ └── DeploymentEntity.java # Add @ElementCollection topics
│ │ ├── mapper/
│ │ │ └── PersistenceDeploymentMapper.java # Add topics to updateEntityFromDomain
│ │ └── repository/
│ │ └── TopicRepository.java # Extend query to UNION deployment topics
│ └── web/dto/
│ ├── deployment/
│ │ ├── CreateDeploymentRequestDto.java # Add @ValidTopics topics field
│ │ └── DeploymentDto.java # Add topics field
│ └── DeploymentInfoDto.java # Add topics field
└── resources/db/migration/
├── H2/V1.47__CreateDeploymentTopicsTable.sql
├── POSTGRES/V1.47__CreateDeploymentTopicsTable.sql
└── MS_SQL_SERVER/V1.47__CreateDeploymentTopicsTable.sql
```

## Design Decisions

### D1: Reuse `@ValidTopics` annotation

The existing `@ValidTopics` + `TopicsValidator` validates: non-blank, ≤255 chars, no leading/trailing whitespace. Apply the same annotation to `CreateDeploymentRequestDto.topics`. No new validation code needed.

### D2: `deployment_topics` join table (separate from `image_definition_topics`)

As discussed — two separate tables preserve FK integrity, cascade delete, and type-safe references. The `deployment` table uses `VARCHAR` ID vs `UUID` on `image_definition`, making a polymorphic table impractical.

### D3: TopicRepository query — UNION approach

Current query:
```sql
select distinct t from ImageDefinitionEntity i join i.topics t order by t
```

Updated to native SQL UNION:
```sql
SELECT DISTINCT topic_name FROM (
SELECT topic_name FROM image_definition_topics
UNION
SELECT topic_name FROM deployment_topics
) AS all_topics ORDER BY topic_name
```

This is simpler and more efficient than two JPQL queries merged in Java. Native query is acceptable here since it's a simple union of two identical-schema tables.

### D4: MapStruct auto-mapping for topics

Since the field name `topics` and type `List<String>` are identical across all layers (DTO → domain → entity), MapStruct will auto-map without explicit `@Mapping` annotations. Only `PersistenceDeploymentMapper.updateEntityFromDomain()` needs a manual line added (it's a hand-written method).

### D5: Export/Import — no mix-in changes needed

`DeploymentExportMixIn` only excludes specific fields (url, status, author, timestamps, imageDefinitionId). Since `topics` is not excluded, it will automatically be included in exports. The import path deserializes via the `Deployment` domain model, which will include topics once the field is added.

### D6: Duplication — automatic via mapper chain

`DeploymentMapper.toCreateCloneDeployment()` calls `toCreateDeployment(deployment)` which is a MapStruct-generated method. Once `topics` exists on both `Deployment` and `CreateDeployment`, the field will be copied automatically.

## Change Inventory

### Layer 1: Database Migration (V1.47)

**H2 and POSTGRES** — `src/main/resources/db/migration/{H2,POSTGRES}/V1.47__CreateDeploymentTopicsTable.sql`:

```sql
CREATE TABLE deployment_topics (
deployment_id VARCHAR(36) NOT NULL,
topic_name VARCHAR(255) NOT NULL,
CONSTRAINT pk_deployment_topics PRIMARY KEY (deployment_id, topic_name),
CONSTRAINT fk_deployment_topics_deployment FOREIGN KEY (deployment_id)
REFERENCES deployment (id) ON DELETE CASCADE
);
```

**MS_SQL_SERVER** — `src/main/resources/db/migration/MS_SQL_SERVER/V1.47__CreateDeploymentTopicsTable.sql`:

```sql
CREATE TABLE deployment_topics (
deployment_id VARCHAR(36) NOT NULL,
topic_name VARCHAR(255) NOT NULL,
CONSTRAINT pk_deployment_topics PRIMARY KEY (deployment_id, topic_name),
CONSTRAINT fk_deployment_topics_deployment FOREIGN KEY (deployment_id)
REFERENCES deployment (id) ON DELETE CASCADE
);
```

Note: `deployment.id` is `VARCHAR(36)` across all vendors (changed from UUID in V1.42). The DDL is identical for this table since it only uses `VARCHAR` columns (no JSON, NVARCHAR, or UUID types that differ across vendors).

### Layer 2: Entity

**File**: `DeploymentEntity.java` — add after `allowedDomains`:

```java
@ElementCollection
@CollectionTable(name = "deployment_topics", joinColumns = @JoinColumn(name = "deployment_id"))
@Column(name = "topic_name")
private List<String> topics;
```

### Layer 3: Domain Models

**File**: `Deployment.java` — add field:
```java
private List<String> topics;
```

**File**: `CreateDeployment.java` — add field:
```java
private List<String> topics;
```

### Layer 4: DTOs

**File**: `CreateDeploymentRequestDto.java` — add after `allowedDomains`:
```java
@Nullable
@ValidTopics
private List<String> topics;
```

**File**: `DeploymentDto.java` — add after `allowedDomains`:
```java
@NotNull
private List<String> allowedDomains; // existing
@Nullable
private List<String> topics;
```

**File**: `DeploymentInfoDto.java` — add to record parameters:
```java
@Nullable List<String> topics
```

### Layer 5: Mappers

**File**: `PersistenceDeploymentMapper.java` — add line in `updateEntityFromDomain()`:
```java
existingEntity.setTopics(updatedEntity.getTopics());
```

All other mappers (DeploymentDtoMapper, DeploymentMapper) will auto-map `topics` via MapStruct.

### Layer 6: TopicRepository

**File**: `TopicRepository.java` — replace JPQL with native UNION query:

```java
public List<String> getAllTopics() {
return entityManager.createNativeQuery("""
SELECT DISTINCT topic_name FROM (
SELECT topic_name FROM image_definition_topics
UNION
SELECT topic_name FROM deployment_topics
) AS all_topics ORDER BY topic_name
""", String.class)
.getResultList();
}
```

### Layer 7: Tests

Update functional tests to cover:
1. Create deployment with topics → topics persisted and returned
2. Create deployment without topics → empty list returned
3. Update deployment topics → topics replaced
4. Delete deployment → topics cascade-deleted
5. Topics listing includes deployment-only topics
6. Topics listing deduplicates across image definitions and deployments
7. Duplicate deployment → topics copied
8. Invalid topics rejected (blank, >255 chars, whitespace-padded)

Existing topic functional tests in `TopicFunctionalTest.java` need extension. Existing deployment tests should continue passing (backward-compatible).
Loading
Loading