Skip to content

Commit 8f6943f

Browse files
authored
🚀 Feat: pipeline execution constraints + adapt pydantic objects (#44)
- New lib/entities/pipeline.py module with Pydantic models: ExecutionResult, Constraints, and Usage - Constraint enforcement in two paths: multiplier pipelines (workflow.py) and normal pipelines (job_processor.py) - Storage layer migration from dicts to PipelineRecord and Job Pydantic models - Frontend UI for setting constraints and displaying usage stats with theme-compatible styling - Comprehensive test coverage for constraint enforcement (13 new tests in test_constraints.py)
1 parent 08c4418 commit 8f6943f

33 files changed

+1920
-246
lines changed

‎.github/instructions/review.instructions.md‎

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,57 @@ review code for quality, security, and consistency. flag anti-patterns, verify d
66

77
## how to use
88

9-
1. **scan for blocking issues** - anti-patterns, security flaws, silent failures
10-
2. **check code quality** - follows llm/rules-backend.md or llm/rules-frontend.md
11-
3. **verify documentation** - identify which llm/state-*.md files need updates
12-
4. **validate tests** - new code has tests, error cases covered
13-
5. **provide verdict** - block, request changes, or approve
9+
1. scan for blocking issues - anti-patterns, security flaws, silent failures
10+
2. check code quality - follows llm/rules-backend.md or llm/rules-frontend.md
11+
3. verify documentation - identify which llm/state-*.md files need updates
12+
4. validate tests - new code has tests, error cases covered
13+
5. provide verdict - block, request changes, or approve
1414

15-
**this file is self-contained.** all rules needed for review are below. do not read external files.
15+
this file is self-contained. all rules needed for review are below. do not read external files.
1616

1717
---
1818

1919
## project context
2020

21-
**type:** full-stack data generation platform (fastapi + react + typescript)
22-
**philosophy:** simplicity over cleverness, clarity over abstraction
23-
**style:** minimal functions, explicit dependencies, fail fast and loud
21+
- type: full-stack data generation platform (fastapi + react + typescript)
22+
- philosophy: simplicity over cleverness, clarity over abstraction
23+
- style: minimal functions, explicit dependencies, fail fast and loud
2424

25-
**llm file structure:**
25+
llm file structure:
2626
- `llm/rules-backend.md` - backend coding standards
2727
- `llm/rules-frontend.md` - frontend coding standards
2828
- `llm/rules-agent.md` - agent behavior guidelines
2929
- `llm/state-backend.md` - backend implementation status
3030
- `llm/state-frontend.md` - frontend implementation status
3131
- `llm/state-project.md` - overall project status
3232

33-
**golden rule:** if code cannot be explained in one sentence, it's too complex.
33+
golden rule: if code cannot be explained in one sentence, it's too complex.
3434

3535
---
3636

3737
## review priorities
3838

39-
**priority 1: blocking issues (must fix)**
39+
priority 1: blocking issues (must fix)
4040
- anti-patterns from checklists below
4141
- security vulnerabilities (sql injection, xss, missing validation)
4242
- silent failures (empty catch/except blocks)
4343
- broken tests
44+
- missing tests for: new api endpoints, new blocks, bug fixes
45+
- hardcoded colors in UI (#000, #fff, rgb() instead of theme variables)
4446

45-
**priority 2: code quality (should fix)**
47+
priority 2: code quality (should fix)
4648
- violations of llm/rules-*.md guidelines
4749
- missing error handling
4850
- missing type hints
4951
- functions >30 lines, >3 params
5052
- classes >7 public methods
5153

52-
**priority 3: documentation (should update)**
54+
priority 3: documentation (should update)
5355
- llm/state-*.md files need updates when architecture changes
5456
- code comments missing for complex logic
5557
- comments explain what instead of why
5658

57-
**priority 4: improvements (nice to have)**
59+
priority 4: improvements (nice to have)
5860
- extract duplicate code
5961
- add memoization where helpful
6062
- improve naming clarity
@@ -64,14 +66,15 @@ review code for quality, security, and consistency. flag anti-patterns, verify d
6466
## backend checklist
6567

6668
### anti-patterns (blocking - must reject)
67-
- [ ] **silent failures** - empty except blocks, no logging
68-
- [ ] **god functions** - >30 lines or >3 params
69-
- [ ] **god classes** - >7 public methods
70-
- [ ] **global variables** - use dependency injection
71-
- [ ] **walrus operators** - complex one-liners violate simplicity
72-
- [ ] **magic numbers/strings** - use named constants
73-
- [ ] **sql injection** - f-strings in queries instead of parameterized
74-
- [ ] **missing error context** - bare exceptions without detail
69+
- [ ] silent failures - empty except blocks, no logging
70+
- [ ] god functions - >30 lines or >3 params
71+
- [ ] god classes - >7 public methods
72+
- [ ] global variables - use dependency injection
73+
- [ ] walrus operators - complex one-liners violate simplicity
74+
- [ ] magic numbers/strings - use named constants
75+
- [ ] sql injection - f-strings in queries instead of parameterized
76+
- [ ] missing error context - bare exceptions without detail
77+
- [ ] missing tests - new api endpoints, new blocks, bug fixes must have tests
7578

7679
### code quality (should fix)
7780
- [ ] specific exceptions caught (never bare `Exception` without re-raise)
@@ -88,9 +91,12 @@ review code for quality, security, and consistency. flag anti-patterns, verify d
8891
- [ ] size limits on file uploads
8992
- [ ] type hints on all parameters and returns
9093
- [ ] `| None` instead of `Optional`
94+
- [ ] entities used instead of big dicts (>5 fields)
9195

9296
### testing
93-
- [ ] tests exist for new features
97+
- [ ] blocking: new api endpoints must have tests
98+
- [ ] blocking: new blocks must have unit tests
99+
- [ ] blocking: bug fixes must have regression tests
94100
- [ ] error cases tested (not just happy path)
95101
- [ ] test names: `test_<method>_<scenario>_<expected>`
96102
- [ ] one behavior per test
@@ -106,16 +112,17 @@ review code for quality, security, and consistency. flag anti-patterns, verify d
106112
## frontend checklist
107113

108114
### anti-patterns (blocking - must reject)
109-
- [ ] **silent error handling** - empty catch blocks
110-
- [ ] **bloated components** - too many hooks, mixed concerns
111-
- [ ] **prop drilling** - >5 props passed through multiple levels
112-
- [ ] **repeated JSX** - copied 3+ times without extraction
113-
- [ ] **direct storage access** - localStorage/sessionStorage not abstracted
114-
- [ ] **inline fetch calls** - not in service layer
115-
- [ ] **unstable dependencies** - missing useCallback/useMemo in hooks
116-
- [ ] **missing cleanup** - useEffect without return for intervals/subscriptions/AbortController
117-
- [ ] **any types** - use proper types or `unknown`
118-
- [ ] **type assertions** - `as` instead of type guards
115+
- [ ] silent error handling - empty catch blocks
116+
- [ ] bloated components - too many hooks, mixed concerns
117+
- [ ] prop drilling - >5 props passed through multiple levels
118+
- [ ] repeated JSX - copied 3+ times without extraction
119+
- [ ] direct storage access - localStorage/sessionStorage not abstracted
120+
- [ ] inline fetch calls - not in service layer
121+
- [ ] unstable dependencies - missing useCallback/useMemo in hooks
122+
- [ ] missing cleanup - useEffect without return for intervals/subscriptions/AbortController
123+
- [ ] any types - use proper types or `unknown`
124+
- [ ] type assertions - `as` instead of type guards
125+
- [ ] hardcoded colors - use theme variables (fg.*, canvas.*, border.*) not #000, #fff, rgb()
119126

120127
### code quality (should fix)
121128
- [ ] components focused (extract if unwieldy)
@@ -153,27 +160,34 @@ review code for quality, security, and consistency. flag anti-patterns, verify d
153160
- [ ] API calls mockable
154161
- [ ] tests exist for new features
155162

163+
### ui/ux
164+
- [ ] theme compatibility verified in both light and dark modes
165+
- [ ] text uses fg.* colors (fg.default, fg.muted, fg.subtle)
166+
- [ ] backgrounds use canvas.* colors
167+
- [ ] no hardcoded colors (#000, #fff, rgb())
168+
- [ ] interactive states work in both themes
169+
156170
---
157171

158172
## documentation updates
159173

160174
### when to update llm/state-*.md files
161175

162-
**llm/state-backend.md** - update when:
176+
llm/state-backend.md - update when:
163177
- new API endpoints added or changed
164178
- database schema modified
165179
- new blocks added to lib/blocks/
166180
- core logic patterns changed (workflow, storage, job processing)
167181
- error handling patterns changed
168182

169-
**llm/state-frontend.md** - update when:
183+
llm/state-frontend.md - update when:
170184
- new pages or components added
171185
- UI flow changed
172186
- state management patterns changed
173187
- API integration patterns changed
174188
- routing updated
175189

176-
**llm/state-project.md** - update when:
190+
llm/state-project.md - update when:
177191
- overall architecture changed
178192
- new major features added
179193
- file structure reorganized
@@ -189,7 +203,7 @@ review code for quality, security, and consistency. flag anti-patterns, verify d
189203
- reflect actual code, not aspirational designs
190204

191205
### code comments
192-
- [ ] complex logic has comments explaining **why** (not what)
206+
- [ ] complex logic has comments explaining why (not what)
193207
- [ ] comments are lowercase and concise
194208
- [ ] no over-documentation of obvious code
195209

@@ -207,13 +221,13 @@ when reviewing refactoring changes (identified by large-scale file changes or sy
207221
- duplicate patterns consolidated
208222

209223
### what to verify
210-
- [ ] **pattern choice is correct** - chosen pattern is actually dominant in codebase (count occurrences)
211-
- [ ] **tests still pass** - no functionality broken
212-
- [ ] **anti-patterns removed** - not just moved around
213-
- [ ] **documentation updated** - llm/state-*.md files reflect changes
214-
- [ ] **quality improved** - code is simpler, clearer, more consistent
215-
- [ ] **behavior unchanged** - unless explicitly documented
216-
- [ ] **no scope creep** - refactoring doesn't include new features
224+
- [ ] pattern choice is correct - chosen pattern is actually dominant in codebase (count occurrences)
225+
- [ ] tests still pass - no functionality broken
226+
- [ ] anti-patterns removed - not just moved around
227+
- [ ] documentation updated - llm/state-*.md files reflect changes
228+
- [ ] quality improved - code is simpler, clearer, more consistent
229+
- [ ] behavior unchanged - unless explicitly documented
230+
- [ ] no scope creep - refactoring doesn't include new features
217231

218232
### acceptable
219233
- renaming for consistency
@@ -236,8 +250,8 @@ when reviewing refactoring changes (identified by large-scale file changes or sy
236250
### step 1: anti-pattern scan
237251
scan code for anti-patterns from checklists above. flag immediately if found.
238252

239-
**backend:** silent failures, god functions, sql injection, magic numbers
240-
**frontend:** silent errors, bloated components, prop drilling, inline fetch
253+
backend: silent failures, god functions, sql injection, magic numbers
254+
frontend: silent errors, bloated components, prop drilling, inline fetch
241255

242256
### step 2: security check
243257
verify no security vulnerabilities:
@@ -314,7 +328,7 @@ identify which llm/state-*.md files need updates:
314328
- code quality: ✓ good | ⚠ issues exist
315329

316330
### verdict
317-
**[block | request changes | approve]**
331+
[block | request changes | approve]
318332

319333
reason: [brief explanation]
320334
```
@@ -335,7 +349,7 @@ reason: [brief explanation]
335349
- fix: `catch (err) { console.error(err); showToast({type: "error", message: err.message}); }`
336350

337351
### verdict
338-
**block** - must fix silent error handling before merge
352+
block - must fix silent error handling before merge
339353
```
340354

341355
### example 2: documentation update needed
@@ -353,7 +367,7 @@ none found
353367
- details: document how user input is sanitized using parameterized queries
354368

355369
### verdict
356-
**request changes** - update state-backend.md to document new pattern
370+
request changes - update state-backend.md to document new pattern
357371
```
358372

359373
### example 3: refactoring review
@@ -384,17 +398,17 @@ refactoring verified:
384398
- ✓ quality improved
385399

386400
### verdict
387-
**request changes** - update state-backend.md then approve
401+
request changes - update state-backend.md then approve
388402
```
389403

390404
---
391405

392406
## golden rules
393407

394-
1. **anti-patterns are blocking** - always reject
395-
2. **security issues are blocking** - always reject
396-
3. **broken tests are blocking** - always reject
397-
4. **llm/* updates required** - for architecture changes
398-
5. **simplicity wins** - if code is complex, it's wrong
399-
6. **fail loudly** - silent failures are never acceptable
400-
7. **self-contained** - all rules in this file, don't read external files
408+
1. anti-patterns are blocking - always reject
409+
2. security issues are blocking - always reject
410+
3. broken tests are blocking - always reject
411+
4. llm/* updates required - for architecture changes
412+
5. simplicity wins - if code is complex, it's wrong
413+
6. fail loudly - silent failures are never acceptable
414+
7. self-contained - all rules in this file, don't read external files

‎app.py‎

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
ConnectionTestResult,
2424
EmbeddingModelConfig,
2525
LLMModelConfig,
26+
PipelineRecord,
2627
Record,
2728
RecordStatus,
2829
RecordUpdate,
@@ -126,7 +127,7 @@ async def validate_seeds(request: SeedValidationRequest) -> dict[str, Any]:
126127
if not pipeline_data:
127128
raise HTTPException(status_code=404, detail="pipeline not found")
128129

129-
blocks = pipeline_data["definition"]["blocks"]
130+
blocks = pipeline_data.definition["blocks"]
130131
if not blocks:
131132
raise HTTPException(status_code=400, detail="pipeline has no blocks")
132133

@@ -172,7 +173,7 @@ async def generate_from_file(
172173
if not pipeline_data:
173174
raise HTTPException(status_code=404, detail="pipeline not found")
174175

175-
pipeline = WorkflowPipeline.load_from_dict(pipeline_data["definition"])
176+
pipeline = WorkflowPipeline.load_from_dict(pipeline_data.definition)
176177

177178
# parse seed file with size limit
178179
content = await file.read(MAX_FILE_SIZE + 1)
@@ -332,10 +333,10 @@ async def get_job(job_id: int) -> dict[str, Any]:
332333
return job
333334

334335
# fallback to database
335-
job = await storage.get_job(job_id)
336-
if not job:
336+
job_obj = await storage.get_job(job_id)
337+
if not job_obj:
337338
raise HTTPException(status_code=404, detail="job not found")
338-
return job
339+
return job_obj.model_dump()
339340

340341

341342
@api_router.delete("/jobs/{job_id}")
@@ -361,7 +362,8 @@ async def list_jobs(pipeline_id: int | None = None) -> list[dict[str, Any]]:
361362
return jobs
362363

363364
# fallback to database
364-
return await storage.list_jobs(pipeline_id=pipeline_id, limit=10)
365+
jobs_list = await storage.list_jobs(pipeline_id=pipeline_id, limit=10)
366+
return [job.model_dump() for job in jobs_list]
365367

366368

367369
@api_router.get("/records")
@@ -476,7 +478,7 @@ async def create_pipeline(pipeline_data: dict[str, Any]) -> dict[str, Any]:
476478

477479

478480
@api_router.get("/pipelines")
479-
async def list_pipelines() -> list[dict[str, Any]]:
481+
async def list_pipelines() -> list[PipelineRecord]:
480482
return await storage.list_pipelines()
481483

482484

@@ -486,10 +488,11 @@ async def get_pipeline(pipeline_id: int) -> dict[str, Any]:
486488
if not pipeline:
487489
raise HTTPException(status_code=404, detail="pipeline not found")
488490

489-
blocks = pipeline.get("definition", {}).get("blocks", [])
490-
pipeline["first_block_is_multiplier"] = is_multiplier_pipeline(blocks)
491+
blocks = pipeline.definition.get("blocks", [])
492+
pipeline_dict = pipeline.model_dump()
493+
pipeline_dict["first_block_is_multiplier"] = is_multiplier_pipeline(blocks)
491494

492-
return pipeline
495+
return pipeline_dict
493496

494497

495498
@api_router.put("/pipelines/{pipeline_id}")
@@ -514,9 +517,30 @@ async def execute_pipeline(pipeline_id: int, data: dict[str, Any]) -> dict[str,
514517
if not pipeline_data:
515518
raise HTTPException(status_code=404, detail="pipeline not found")
516519

517-
pipeline = WorkflowPipeline.load_from_dict(pipeline_data["definition"])
518-
result, trace, trace_id = await pipeline.execute(data)
519-
return {"result": result, "trace": trace, "trace_id": trace_id}
520+
pipeline = WorkflowPipeline.load_from_dict(pipeline_data.definition)
521+
exec_result = await pipeline.execute(data)
522+
# handle both ExecutionResult and list[ExecutionResult]
523+
if isinstance(exec_result, list):
524+
# multiplier pipeline
525+
return {
526+
"results": [
527+
{
528+
"result": r.result,
529+
"trace": r.trace,
530+
"trace_id": r.trace_id,
531+
"usage": r.usage,
532+
}
533+
for r in exec_result
534+
]
535+
}
536+
else:
537+
# normal pipeline
538+
return {
539+
"result": exec_result.result,
540+
"trace": exec_result.trace,
541+
"trace_id": exec_result.trace_id,
542+
"usage": exec_result.usage,
543+
}
520544
except HTTPException:
521545
# Let HTTPException propagate to FastAPI
522546
raise
@@ -538,7 +562,7 @@ async def get_accumulated_state_schema(pipeline_id: int) -> dict[str, list[str]]
538562
if not pipeline_data:
539563
raise HTTPException(status_code=404, detail="pipeline not found")
540564

541-
blocks = pipeline_data["definition"]["blocks"]
565+
blocks = pipeline_data.definition["blocks"]
542566
fields = compute_accumulated_state_schema(blocks)
543567
return {"fields": fields}
544568

@@ -585,7 +609,7 @@ async def delete_pipeline(pipeline_id: int) -> dict[str, bool]:
585609

586610
# remove jobs from in-memory queue
587611
for job in jobs:
588-
job_queue.delete_job(job["id"])
612+
job_queue.delete_job(job.id)
589613

590614
return {"success": True}
591615

0 commit comments

Comments
 (0)