chore: auto-commit after complete-milestone
GSD-Unit: M001
This commit is contained in:
parent
2d8efb15dd
commit
c3c5a9b082
10 changed files with 483 additions and 9 deletions
|
|
@ -8,4 +8,5 @@
|
||||||
|---|------|-------|----------|--------|-----------|------------|---------|
|
|---|------|-------|----------|--------|-----------|------------|---------|
|
||||||
| D001 | | architecture | Engine/App architectural relationship | Engine is standalone module, App is a consumer. Zero coupling — App calls Engine only via HTTP API. | Engine is proprietary IP that must be embeddable into future applications independently. Clean input/output contracts enable this. | No | human |
|
| D001 | | architecture | Engine/App architectural relationship | Engine is standalone module, App is a consumer. Zero coupling — App calls Engine only via HTTP API. | Engine is proprietary IP that must be embeddable into future applications independently. Clean input/output contracts enable this. | No | human |
|
||||||
| D002 | | architecture | Build order and gating strategy | Build Engine first (M001), then App canvas (M002), then Export+Deploy+Embed (M003). Human checkpoints gate each transition. | Brief explicitly mandates: validate engine output quality before building canvas UI, validate canvas before export/deploy. Engine is the hardest and most valuable piece. | No | human |
|
| D002 | | architecture | Build order and gating strategy | Build Engine first (M001), then App canvas (M002), then Export+Deploy+Embed (M003). Human checkpoints gate each transition. | Brief explicitly mandates: validate engine output quality before building canvas UI, validate canvas before export/deploy. Engine is the hardest and most valuable piece. | No | human |
|
||||||
| D003 | | engine | DXF output response format | DXF returns raw bytes with application/dxf content-type; metadata goes in X-Kerf-Metadata response header as JSON | DXF is binary data that shouldn't be JSON-encoded. CAD tools and download clients need raw bytes. Metadata is still accessible via response header for API consumers who need path_count, node_count, etc. | Yes | agent |
|
| D003 | | engine | Post-processing replaces regex metadata extraction | postprocess_svg() pipeline replaces old _extract_svg_metadata() regex approach; all SVG paths are parsed into structured PathInfo objects with coordinates, area, island status | Structured path data is needed for DXF/JSON output generation, RDP simplification, and island detection. XML parsing is now necessary since we need actual coordinate data, not just counts. This supersedes D003's regex approach. | No | agent |
|
||||||
|
| D004 | | architecture | Engine Docker image build strategy | Multi-stage build: builder stage compiles pypotrace with build-essential/libagg-dev/libpotrace-dev, runtime stage uses python:3.11-slim with only runtime libs (libpotrace0, libagg2, curl). Only engine source is copied — no App code. | Multi-stage keeps the runtime image small by excluding build tools. Copying only engine source enforces the Engine/App separation (D001). curl is included for Docker HEALTHCHECK. | Yes | agent |
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ Agents read this before every unit. Add entries when you discover something wort
|
||||||
|---|---------|-------|-------|
|
|---|---------|-------|-------|
|
||||||
| P001 | Test images generated programmatically via numpy | engine/tests/ | No fixture image files checked in. Tests create shapes (rectangles, circles) with numpy + cv2. Continue this pattern in S02/S03. |
|
| P001 | Test images generated programmatically via numpy | engine/tests/ | No fixture image files checked in. Tests create shapes (rectangles, circles) with numpy + cv2. Continue this pattern in S02/S03. |
|
||||||
| P002 | Tests must use .venv/bin/python -m pytest | engine/ | No system-wide `python` on PATH. Bare `python -m pytest` fails with exit 127. Always use the venv binary. |
|
| P002 | Tests must use .venv/bin/python -m pytest | engine/ | No system-wide `python` on PATH. Bare `python -m pytest` fails with exit 127. Always use the venv binary. |
|
||||||
|
| P003 | PostProcessResult is the universal intermediate representation | engine/pipeline/postprocess.py → engine/output/ | All output generators (DXF, JSON, SVG) consume PostProcessResult. Never generate output directly from raw SVG strings. |
|
||||||
|
| P004 | _format_response() for consistent multi-format API responses | engine/api/routes.py | DXF returns binary Response with X-Kerf-Metadata header; SVG/JSON return JSON envelopes. Use _format_response() for both /engine/trace and /engine/simplify. |
|
||||||
|
| P005 | Preset-driven pipeline: resolve_params() merges preset → user | engine/presets/loader.py, engine/api/routes.py | Presets define defaults for all pipeline stages. User params override. Mode comes from preset unless user explicitly sets it. |
|
||||||
|
|
||||||
## Lessons Learned
|
## Lessons Learned
|
||||||
|
|
||||||
|
|
@ -25,3 +28,5 @@ Agents read this before every unit. Add entries when you discover something wort
|
||||||
| L004 | ezdxf emits pyparsing deprecation warnings in tests | ezdxf uses deprecated pyparsing method names (`addParseAction`, `oneOf`, etc.) | These are harmless library warnings — don't try to fix them. Filter with `pytest -W ignore::DeprecationWarning` if noisy. | engine tests |
|
| L004 | ezdxf emits pyparsing deprecation warnings in tests | ezdxf uses deprecated pyparsing method names (`addParseAction`, `oneOf`, etc.) | These are harmless library warnings — don't try to fix them. Filter with `pytest -W ignore::DeprecationWarning` if noisy. | engine tests |
|
||||||
| L005 | DXF output format needs binary response, not JSON envelope | DXF files are binary/text data not suitable for JSON embedding | Use FastAPI `Response(content=bytes, media_type="application/dxf")` with metadata in `X-Kerf-Metadata` header | engine API |
|
| L005 | DXF output format needs binary response, not JSON envelope | DXF files are binary/text data not suitable for JSON embedding | Use FastAPI `Response(content=bytes, media_type="application/dxf")` with metadata in `X-Kerf-Metadata` header | engine API |
|
||||||
| L006 | postprocess_svg() fully parses SVG paths into coordinates | XML parsing + path d-attribute parsing gives structured PathInfo objects | This replaces the old regex-based SVG metadata extraction (D003). All output generators (DXF, JSON, SVG) consume PostProcessResult. | engine pipeline |
|
| L006 | postprocess_svg() fully parses SVG paths into coordinates | XML parsing + path d-attribute parsing gives structured PathInfo objects | This replaces the old regex-based SVG metadata extraction (D003). All output generators (DXF, JSON, SVG) consume PostProcessResult. | engine pipeline |
|
||||||
|
| L007 | Docker build context must be project root, not engine/ | Dockerfile.engine uses `COPY engine/...` paths relative to project root | Always build with `docker build -f docker/Dockerfile.engine -t kerf-engine:dev .` from project root | Docker |
|
||||||
|
| L008 | Engine container has dual health endpoints | /health on root (main.py) and /engine/health on router (routes.py) both exist | Use /engine/health for Docker HEALTHCHECK and external probes for namespace consistency | engine API, Docker |
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,5 @@
|
||||||
{"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S02"},"ts":"2026-03-26T04:41:58.014Z","actor":"agent","hash":"296c12d4a2f536c8","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
{"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S02"},"ts":"2026-03-26T04:41:58.014Z","actor":"agent","hash":"296c12d4a2f536c8","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
||||||
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T01"},"ts":"2026-03-26T04:45:48.732Z","actor":"agent","hash":"a1c0c74b1d7c5d15","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T01"},"ts":"2026-03-26T04:45:48.732Z","actor":"agent","hash":"a1c0c74b1d7c5d15","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
||||||
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T02"},"ts":"2026-03-26T04:49:33.566Z","actor":"agent","hash":"fc8b517936769f11","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T02"},"ts":"2026-03-26T04:49:33.566Z","actor":"agent","hash":"fc8b517936769f11","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
||||||
|
{"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S03"},"ts":"2026-03-26T04:52:09.459Z","actor":"agent","hash":"c346f9b623e5659d","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
||||||
|
{"cmd":"complete-milestone","params":{"milestoneId":"M001"},"ts":"2026-03-26T04:56:43.004Z","actor":"agent","hash":"c877176040436ab9","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ Build and validate the standalone Kerf Engine: a stateless HTTP API that accepts
|
||||||
|----|-------|------|---------|------|------------|
|
|----|-------|------|---------|------|------------|
|
||||||
| S01 | Core Pipeline — Preprocessing + Vectorization | high — dependency installation, opencv+potrace+vtracer integration | — | ✅ | POST /engine/trace with a PNG logo returns valid SVG using both Potrace and VTracer modes |
|
| S01 | Core Pipeline — Preprocessing + Vectorization | high — dependency installation, opencv+potrace+vtracer integration | — | ✅ | POST /engine/trace with a PNG logo returns valid SVG using both Potrace and VTracer modes |
|
||||||
| S02 | Post-Processing + Output Formats (SVG, DXF, JSON) | high — dxf generation quality is hard to validate programmatically | S01 | ✅ | /engine/trace returns valid DXF and JSON output; /engine/simplify reduces node count on complex SVG |
|
| S02 | Post-Processing + Output Formats (SVG, DXF, JSON) | high — dxf generation quality is hard to validate programmatically | S01 | ✅ | /engine/trace returns valid DXF and JSON output; /engine/simplify reduces node count on complex SVG |
|
||||||
| S03 | Preset System + Engine Docker Packaging | low — presets are config files; docker packaging is well-understood | S02 | ⬜ | GET /engine/presets returns all presets; each preset produces distinct output from same input; engine runs in Docker |
|
| S03 | Preset System + Engine Docker Packaging | low — presets are config files; docker packaging is well-understood | S02 | ✅ | GET /engine/presets returns all presets; each preset produces distinct output from same input; engine runs in Docker |
|
||||||
|
|
|
||||||
94
.gsd/milestones/M001/M001-SUMMARY.md
Normal file
94
.gsd/milestones/M001/M001-SUMMARY.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
---
|
||||||
|
id: M001
|
||||||
|
title: "Kerf Engine — Raster-to-Vector Pipeline & API"
|
||||||
|
status: complete
|
||||||
|
completed_at: 2026-03-26T04:56:42.994Z
|
||||||
|
key_decisions:
|
||||||
|
- D001: Engine is standalone module, App is consumer — zero coupling via HTTP API only
|
||||||
|
- D003: postprocess_svg() pipeline replaces regex-based SVG metadata extraction with full path parsing into structured PathInfo objects
|
||||||
|
- D004: Multi-stage Docker build — builder compiles pypotrace, runtime uses python:3.11-slim with only engine source
|
||||||
|
- Bilateral filter chosen over Gaussian for edge-preserving denoise in preprocessing
|
||||||
|
- DXF output as raw bytes with metadata in X-Kerf-Metadata header (not JSON envelope)
|
||||||
|
- Preset default is 'sign' — covers the most common use case
|
||||||
|
- Bezier curves linearized for RDP simplification and DXF polyline generation
|
||||||
|
key_files:
|
||||||
|
- engine/main.py — FastAPI app entry point
|
||||||
|
- engine/api/routes.py — All API endpoints: /engine/trace, /engine/simplify, /engine/presets, /engine/health
|
||||||
|
- engine/pipeline/preprocessing.py — OpenCV preprocessing pipeline (grayscale, denoise, CLAHE, threshold, morphology)
|
||||||
|
- engine/pipeline/vectorize.py — Potrace + VTracer dual-mode vectorization
|
||||||
|
- engine/pipeline/postprocess.py — SVG path parsing, RDP simplification, island detection, open path repair (414 lines)
|
||||||
|
- engine/output/dxf.py — AC1015 DXF generator with LWPOLYLINE entities and island layer separation
|
||||||
|
- engine/output/json_output.py — Structured JSON output with path commands and properties
|
||||||
|
- engine/output/svg.py — SVG output generator from PostProcessResult
|
||||||
|
- engine/presets/loader.py — Preset loader with caching, listing, and merge-based param resolution
|
||||||
|
- engine/presets/sign.json — Sign preset (aggressive simplification for signage)
|
||||||
|
- engine/presets/patch.json — Patch preset (smooth curves, auto-close for embroidery)
|
||||||
|
- engine/presets/stencil.json — Stencil preset (heavy simplification, fixed threshold)
|
||||||
|
- engine/presets/detailed.json — Detailed preset (max fidelity for illustrations)
|
||||||
|
- engine/presets/custom.json — Custom preset (empty defaults, user controls everything)
|
||||||
|
- docker/Dockerfile.engine — Multi-stage Docker build with HEALTHCHECK
|
||||||
|
- engine/tests/ — 196 tests across 6 test files
|
||||||
|
lessons_learned:
|
||||||
|
- L001: pypotrace requires system packages (libpotrace-dev, libagg-dev, pkg-config) — must be installed before pip install
|
||||||
|
- L002: VTracer Python bindings work directly via convert_raw_image_to_svg() — no subprocess needed
|
||||||
|
- L003: pypotrace Bitmap requires uint32 data — cast with (img > 0).astype(np.uint32) to avoid segfaults
|
||||||
|
- L005: DXF output needs binary Response with X-Kerf-Metadata header, not JSON envelope
|
||||||
|
- L006: Full SVG path parsing into coordinates is necessary for DXF/JSON output — regex counting is insufficient
|
||||||
|
- L007: Docker build context must be project root, not engine/ — Dockerfile uses COPY engine/ paths
|
||||||
|
- Programmatic test images via numpy eliminate fixture file management and make tests self-contained
|
||||||
|
- Tests must use .venv/bin/python -m pytest — no system python on PATH
|
||||||
|
---
|
||||||
|
|
||||||
|
# M001: Kerf Engine — Raster-to-Vector Pipeline & API
|
||||||
|
|
||||||
|
**Shipped a stateless FastAPI raster-to-vector engine with OpenCV preprocessing, dual vectorization modes (Potrace + VTracer), RDP post-processing with island detection, three output formats (SVG, DXF, JSON), a 5-preset configuration system, and Docker packaging — 196 tests passing.**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
M001 built the complete Kerf Engine — the proprietary raster-to-vector conversion core — across three slices.
|
||||||
|
|
||||||
|
**S01 (Core Pipeline)** stood up the FastAPI project with an OpenCV preprocessing pipeline (grayscale, bilateral filter, CLAHE, Otsu thresholding, morphological ops) and dual vectorization: Potrace for binary bitmap tracing and VTracer for color/grayscale tracing. POST /engine/trace accepts PNG uploads and returns SVG output with metadata. 76 tests established the foundation and patterns (programmatic test images via numpy, .venv pytest runner).
|
||||||
|
|
||||||
|
**S02 (Post-Processing + Output Formats)** added the post-processing layer that parses raw SVG paths into structured PathInfo objects, implements Ramer-Douglas-Peucker simplification, detects islands via signed-area calculation, and identifies open paths. Three output generators consume PostProcessResult: DXF (AC1015 with LWPOLYLINE entities, islands on separate layer), JSON (structured path commands with properties), and SVG (simplified output). A /engine/simplify endpoint was added for SVG-to-SVG/DXF/JSON conversion. The old regex-based metadata extraction was replaced entirely by full path parsing — a necessary evolution to support DXF coordinate output and island detection.
|
||||||
|
|
||||||
|
**S03 (Presets + Docker)** wrapped the milestone with a preset system (5 tuned configs: sign, patch, stencil, detailed, custom) that drives the entire pipeline via merge-based param resolution (preset defaults + user overrides). GET /engine/presets exposes all configs. Docker packaging uses a multi-stage build — builder stage compiles pypotrace with C dependencies, runtime stage uses python:3.11-slim with only engine source (no App code), enforcing the Engine/App separation mandated by D001. Container includes HEALTHCHECK on /engine/health.
|
||||||
|
|
||||||
|
The slices integrated cleanly: S01's vectorizer output feeds S02's postprocess_svg(), which produces PostProcessResult consumed by output generators; S03's preset system configures all three pipeline stages. routes.py evolved across all slices with no regressions — the final 196-test suite validates every endpoint and format combination.
|
||||||
|
|
||||||
|
## Success Criteria Results
|
||||||
|
|
||||||
|
### S01: "POST /engine/trace with a PNG logo returns valid SVG using both Potrace and VTracer modes"
|
||||||
|
- ✅ **MET** — POST /engine/trace endpoint exists and works. Integration tests confirm both Potrace and VTracer modes produce valid SVG with metadata (path_count, node_count_total, open_paths, processing_ms). 6 mode-specific tests pass.
|
||||||
|
|
||||||
|
### S02: "/engine/trace returns valid DXF and JSON output; /engine/simplify reduces node count on complex SVG"
|
||||||
|
- ✅ **MET** — output_format parameter routes to SVG/DXF/JSON. DXF returns binary with X-Kerf-Metadata header. JSON returns structured path arrays. /engine/simplify accepts SVG + epsilon and returns simplified output in all formats. 22 format/simplify tests pass including test_simplify_reduces_nodes.
|
||||||
|
|
||||||
|
### S03: "GET /engine/presets returns all presets; each preset produces distinct output from same input; engine runs in Docker"
|
||||||
|
- ✅ **MET** — GET /engine/presets returns all 5 presets. test_presets_produce_different_node_counts confirms cross-preset differentiation. Docker image builds from multi-stage Dockerfile, starts healthy, serves /engine/health. 28 preset tests pass.
|
||||||
|
|
||||||
|
## Definition of Done Results
|
||||||
|
|
||||||
|
- ✅ All 3 slices marked complete (S01 ✅, S02 ✅, S03 ✅)
|
||||||
|
- ✅ All slice summaries exist on disk (S01-SUMMARY.md, S02-SUMMARY.md, S03-SUMMARY.md)
|
||||||
|
- ✅ 196 tests pass in 1.24s with exit 0
|
||||||
|
- ✅ 31 non-GSD source files created (3,619 lines of code)
|
||||||
|
- ✅ Cross-slice integration verified: S01→S02 pipeline path works, S02→S03 preset-driven flow works, all endpoints coexist in routes.py
|
||||||
|
- ✅ Validation passed with verdict: pass (M001-VALIDATION.md)
|
||||||
|
|
||||||
|
## Requirement Outcomes
|
||||||
|
|
||||||
|
No formal requirements (REQUIREMENTS.md) were defined for this milestone. Governance was via architectural decisions (D001-D004) and per-slice success criteria.
|
||||||
|
|
||||||
|
**Decision compliance:**
|
||||||
|
- D001 (Engine/App separation): Validated — Docker image contains only engine source, zero App coupling.
|
||||||
|
- D002 (Build order): Validated — Engine built first as mandated. M002/M003 deferred.
|
||||||
|
- D003 (Post-processing replaces regex): Validated — postprocess_svg() fully supersedes regex extraction.
|
||||||
|
- D004 (Multi-stage Docker build): Validated — builder compiles pypotrace, runtime uses slim image.
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
D003 (regex metadata extraction) was superseded mid-milestone — postprocess_svg() replaced it entirely with full XML path parsing, which was necessary for DXF/JSON output generation. Docker build context correction: plan referenced /vmPool/r/repos/xpltdco/kerf which doesn't exist — actual context is project root. Added /engine/health alongside /health for namespace consistency (not originally planned). Postprocessing params (auto_close, close_tolerance) exposed through preset system beyond original plan scope.
|
||||||
|
|
||||||
|
## Follow-ups
|
||||||
|
|
||||||
|
Consider adding preset validation on load (schema check). Consider documenting pipeline defaults in the custom preset for discoverability. Future M002 will consume the engine container as a service for the App canvas UI. Future M003 handles export, deployment, and embed functionality.
|
||||||
92
.gsd/milestones/M001/M001-VALIDATION.md
Normal file
92
.gsd/milestones/M001/M001-VALIDATION.md
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
---
|
||||||
|
verdict: pass
|
||||||
|
remediation_round: 0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Milestone Validation: M001
|
||||||
|
|
||||||
|
## Success Criteria Checklist
|
||||||
|
## Success Criteria Checklist
|
||||||
|
|
||||||
|
The M001 roadmap defines success via per-slice "After this" demo criteria (no separate success criteria section). Each is evaluated below:
|
||||||
|
|
||||||
|
### S01: "POST /engine/trace with a PNG logo returns valid SVG using both Potrace and VTracer modes"
|
||||||
|
- [x] **PASS** — `POST /engine/trace` endpoint exists in `engine/api/routes.py`. S01 summary confirms 14 integration tests covering both Potrace and VTracer modes. S01-UAT test case 7 verifies the response shape contract. 76 tests pass including mode-specific tests (`TestTraceEndpointPotrace`, `TestTraceEndpointVtracer`). Full test suite re-run confirms 196 tests pass (S01 tests still green).
|
||||||
|
|
||||||
|
### S02: "/engine/trace returns valid DXF and JSON output; /engine/simplify reduces node count on complex SVG"
|
||||||
|
- [x] **PASS** — `output_format` parameter routing confirmed in routes.py (svg/dxf/json). DXF generator (`engine/output/dxf.py`) produces AC1015 DXF with LWPOLYLINE entities. JSON generator (`engine/output/json_output.py`) outputs structured path arrays. `/engine/simplify` endpoint confirmed in routes.py. 35 API integration tests cover all format combinations for both endpoints. S02-UAT defines test cases for DXF binary response with X-Kerf-Metadata header, JSON path structure, and simplification node reduction.
|
||||||
|
|
||||||
|
### S03: "GET /engine/presets returns all presets; each preset produces distinct output from same input; engine runs in Docker"
|
||||||
|
- [x] **PASS** — `GET /engine/presets` endpoint confirmed in routes.py. All 5 preset JSON files verified on disk (sign, patch, stencil, detailed, custom) with correct structure (preprocessing/vectorization/postprocessing sections). 28 preset-specific tests include cross-preset output differentiation tests. `docker/Dockerfile.engine` implements multi-stage build with HEALTHCHECK. S03 summary confirms container builds, starts healthy, and serves all endpoints.
|
||||||
|
|
||||||
|
## Slice Delivery Audit
|
||||||
|
## Slice Delivery Audit
|
||||||
|
|
||||||
|
| Slice | Claimed Deliverable | Delivered? | Evidence |
|
||||||
|
|-------|-------------------|-----------|----------|
|
||||||
|
| S01 | FastAPI server with /engine/trace, OpenCV preprocessing, Potrace + VTracer vectorization | ✅ Yes | `engine/main.py`, `engine/api/routes.py` (POST /engine/trace), `engine/pipeline/preprocessing.py` (OpenCV pipeline), `engine/pipeline/vectorize.py` (potrace_trace + vtracer_trace). 76 tests passing. |
|
||||||
|
| S01 | 76 tests | ✅ Yes | All included in 196-test suite; original S01 tests still pass. |
|
||||||
|
| S02 | postprocess_svg() with RDP simplification, island detection, node counting | ✅ Yes | `engine/pipeline/postprocess.py` (414 lines) with PathInfo objects, RDP, island detection via signed-area. |
|
||||||
|
| S02 | Output generators: DXF, JSON, SVG | ✅ Yes | `engine/output/dxf.py` (66 lines, ezdxf AC1015), `engine/output/json_output.py` (76 lines), `engine/output/svg.py` (22 lines). |
|
||||||
|
| S02 | /engine/simplify endpoint | ✅ Yes | Confirmed in routes.py — POST /engine/simplify with epsilon + output_format params. |
|
||||||
|
| S02 | output_format routing on /engine/trace | ✅ Yes | Confirmed in routes.py — accepts svg/dxf/json. |
|
||||||
|
| S02 | 169 tests | ✅ Yes | All included in 196-test suite. |
|
||||||
|
| S03 | 5 presets (sign, patch, stencil, detailed, custom) | ✅ Yes | All 5 JSON files present in `engine/presets/` with correct structure verified. |
|
||||||
|
| S03 | Preset loader with param merge/resolution | ✅ Yes | `engine/presets/loader.py` with resolve_params() — user overrides preset defaults. |
|
||||||
|
| S03 | GET /engine/presets endpoint | ✅ Yes | Confirmed in routes.py. |
|
||||||
|
| S03 | Docker image with healthcheck | ✅ Yes | `docker/Dockerfile.engine` — multi-stage build, HEALTHCHECK pings /engine/health. |
|
||||||
|
| S03 | GET /engine/health endpoint | ✅ Yes | Confirmed in routes.py. |
|
||||||
|
| S03 | 196 total tests | ✅ Yes | Full suite run: `196 passed, 21 warnings in 0.96s`. |
|
||||||
|
|
||||||
|
## Cross-Slice Integration
|
||||||
|
## Cross-Slice Integration
|
||||||
|
|
||||||
|
### S01 → S02 Boundary
|
||||||
|
- **Contract:** S01 produces raw SVG strings from potrace_trace() and vtracer_trace(). S02 consumes these via postprocess_svg().
|
||||||
|
- **Status:** ✅ Aligned. S02 summary confirms postprocess_svg() replaced the old regex metadata extraction (D003). PostProcessResult is the universal intermediate representation. 169 tests pass including integration tests that exercise the full S01→S02 pipeline path.
|
||||||
|
- **Response shape evolution:** S01 defined `{output, format, metadata: {path_count, node_count_total, open_paths, warnings, processing_ms}}`. S02 extended metadata to include `island_count`. This is additive — backward compatible.
|
||||||
|
|
||||||
|
### S02 → S03 Boundary
|
||||||
|
- **Contract:** S03 consumes the post-processing pipeline and output format generators. Presets configure the full pipeline (preprocessing + vectorization + postprocessing params).
|
||||||
|
- **Status:** ✅ Aligned. S03 summary confirms resolve_params() merges preset defaults with user overrides. Presets contain preprocessing, vectorization, and postprocessing sections that feed directly into the pipeline stages built in S01/S02. 28 preset integration tests validate end-to-end preset→pipeline→output flow.
|
||||||
|
|
||||||
|
### Cross-cutting: routes.py
|
||||||
|
- The API router (routes.py) was modified by all three slices. Final state (7364 bytes) contains all endpoints from all slices: /engine/health, /engine/presets, /engine/trace, /engine/simplify. No regressions — 196 tests confirm all endpoints work together.
|
||||||
|
|
||||||
|
### No boundary mismatches detected.
|
||||||
|
|
||||||
|
## Requirement Coverage
|
||||||
|
## Requirement Coverage
|
||||||
|
|
||||||
|
No formal requirements (REQUIREMENTS.md) were defined for this milestone. The project uses decision records (D001–D004) and per-slice success criteria as the governing specification.
|
||||||
|
|
||||||
|
**Decision compliance check:**
|
||||||
|
- **D001** (Engine/App separation): ✅ Docker image copies only engine source — no App code. Engine is a standalone HTTP API with no App dependencies.
|
||||||
|
- **D002** (Build order): ✅ M001 builds Engine first as specified. App canvas (M002) and Export/Deploy (M003) are deferred.
|
||||||
|
- **D003** (Post-processing replaces regex): ✅ Superseded as documented. postprocess_svg() provides structured PathInfo objects.
|
||||||
|
- **D004** (Multi-stage Docker build): ✅ Implemented exactly as specified — builder compiles pypotrace, runtime uses slim image with only runtime libs.
|
||||||
|
|
||||||
|
No unaddressed requirements exist.
|
||||||
|
|
||||||
|
## Verdict Rationale
|
||||||
|
**Verdict: PASS**
|
||||||
|
|
||||||
|
All three slices delivered exactly what the roadmap specified:
|
||||||
|
|
||||||
|
1. **S01** — Core pipeline with preprocessing + dual vectorization modes (Potrace/VTracer) via POST /engine/trace. 76 tests established the foundation.
|
||||||
|
|
||||||
|
2. **S02** — Full post-processing (RDP simplification, island detection, open path repair) with three output format generators (SVG, DXF, JSON). /engine/simplify endpoint added. Test suite grew to 169.
|
||||||
|
|
||||||
|
3. **S03** — 5 tuned presets with merge-based param resolution, GET /engine/presets endpoint, and multi-stage Docker image with healthcheck. Final test suite: 196 tests, all passing in under 1 second.
|
||||||
|
|
||||||
|
**Evidence is strong:**
|
||||||
|
- 196 tests pass (verified by live test run, 0.96s, exit 0)
|
||||||
|
- All source files exist on disk with expected structure
|
||||||
|
- All 4 API endpoints confirmed in routes.py
|
||||||
|
- All 5 preset configs verified with correct JSON structure
|
||||||
|
- Dockerfile exists with multi-stage build and HEALTHCHECK
|
||||||
|
- Cross-slice integration points align — no boundary mismatches
|
||||||
|
- All 4 architectural decisions (D001–D004) are satisfied
|
||||||
|
- UAT artifacts exist for all 3 slices with comprehensive test cases
|
||||||
|
|
||||||
|
No gaps, regressions, or missing deliverables found. The milestone is ready for completion.
|
||||||
106
.gsd/milestones/M001/slices/S03/S03-SUMMARY.md
Normal file
106
.gsd/milestones/M001/slices/S03/S03-SUMMARY.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
---
|
||||||
|
id: S03
|
||||||
|
parent: M001
|
||||||
|
milestone: M001
|
||||||
|
provides:
|
||||||
|
- Preset system with 5 tuned configs and merge-based param resolution
|
||||||
|
- GET /engine/presets endpoint
|
||||||
|
- Docker image kerf-engine:dev with healthcheck
|
||||||
|
- GET /engine/health endpoint
|
||||||
|
requires:
|
||||||
|
- slice: S02
|
||||||
|
provides: Post-processing pipeline and output format generators (SVG, DXF, JSON) consumed by preset-driven trace flow
|
||||||
|
affects:
|
||||||
|
[]
|
||||||
|
key_files:
|
||||||
|
- engine/presets/sign.json
|
||||||
|
- engine/presets/patch.json
|
||||||
|
- engine/presets/stencil.json
|
||||||
|
- engine/presets/detailed.json
|
||||||
|
- engine/presets/custom.json
|
||||||
|
- engine/presets/loader.py
|
||||||
|
- engine/api/routes.py
|
||||||
|
- engine/tests/test_presets.py
|
||||||
|
- docker/Dockerfile.engine
|
||||||
|
- .dockerignore
|
||||||
|
key_decisions:
|
||||||
|
- Preset default is 'sign' — covers the most common use case
|
||||||
|
- Presets use flat JSON with three sections (preprocessing, vectorization, postprocessing)
|
||||||
|
- resolve_params merges preset → user_params with user taking precedence
|
||||||
|
- Custom preset has empty param sections so pipeline defaults apply unless user provides overrides
|
||||||
|
- Multi-stage Docker build separates build deps from runtime (smaller image, no compiler tools)
|
||||||
|
- Engine image contains only engine source — enforces Engine/App separation (D001)
|
||||||
|
- Health endpoint at /engine/health for namespace consistency with other /engine/* routes
|
||||||
|
patterns_established:
|
||||||
|
- Preset-driven pipeline configuration: presets define defaults, user params override
|
||||||
|
- Multi-stage Docker build pattern for Python+C extension packages (pypotrace)
|
||||||
|
- Dual health endpoints: /health (root) for simple checks, /engine/health (namespaced) for Docker/orchestration
|
||||||
|
observability_surfaces:
|
||||||
|
- GET /engine/health — container health probe endpoint
|
||||||
|
- GET /engine/presets — returns all available presets with full config (useful for debugging preset state)
|
||||||
|
- Docker HEALTHCHECK — automatic container health monitoring (15s interval, 3 retries)
|
||||||
|
drill_down_paths:
|
||||||
|
- .gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md
|
||||||
|
- .gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-03-26T04:52:09.425Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# S03: Preset System + Engine Docker Packaging
|
||||||
|
|
||||||
|
**Shipped 5 pipeline presets (sign, patch, stencil, detailed, custom) with merge-based param resolution, GET /engine/presets endpoint, and a multi-stage Docker image with healthcheck that runs the engine standalone.**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
This slice wrapped up the M001 engine milestone with two deliverables: a preset system and Docker packaging.
|
||||||
|
|
||||||
|
**T01 — Preset System:** Created engine/presets/ package with 5 JSON config files, each tuned for a distinct vectorization use case: sign (aggressive simplification for signage), patch (smooth curves with auto-close for embroidery patches), stencil (heavy simplification with fixed threshold), detailed (max fidelity for illustrations), and custom (empty defaults — all params from user). Built loader.py with in-memory caching and resolve_params() that merges preset defaults with user-supplied overrides (user wins on conflicts). Mode now comes from the preset unless the user explicitly overrides it. Added GET /engine/presets endpoint that returns all available presets with their full config. Wired preset selection into the /engine/trace pipeline so posting with `preset: "stencil"` applies that preset's params. Postprocessing params (close_tolerance, auto_close) are now also preset-driven. 28 preset-specific tests validate loader logic, param resolution, endpoint response shape, per-preset integration, and cross-preset output differentiation.
|
||||||
|
|
||||||
|
**T02 — Docker Packaging:** Created docker/Dockerfile.engine as a two-stage build. Builder stage installs build-essential, libagg-dev, libpotrace-dev to compile pypotrace, installs all Python deps into /opt/venv. Runtime stage uses python:3.11-slim with only runtime libs (libpotrace0, libagg2, curl), copies the venv and only engine source — no App code. Added GET /engine/health returning {"status":"ok"} on the router for namespace consistency. Docker HEALTHCHECK instruction pings /engine/health (15s interval, 5s timeout, 10s start-period, 3 retries). Container builds cleanly, starts, reports healthy, and serves all engine endpoints.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
All 196 tests pass (28 preset-specific). Docker image builds from project root with `docker build -f docker/Dockerfile.engine -t kerf-engine:dev .` (exit 0). Container starts on port 8100, /engine/health returns {"status":"ok"}, Docker health status shows "healthy" with 0 failing streak, /engine/presets returns all 5 presets with full config. Container contains only engine source — no App files.
|
||||||
|
|
||||||
|
## Requirements Advanced
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Requirements Validated
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## New Requirements Surfaced
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Requirements Invalidated or Re-scoped
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
T02 verification command in the plan used `cd /vmPool/r/repos/xpltdco/kerf` which doesn't exist — the correct build context is the project root at `/home/aux/development/xpltdco/kerf-engine`. T01 added auto_close and close_tolerance postprocessing params to the preset-driven trace flow (already supported by postprocess_svg but not previously exposed through the API). T02 added /engine/health alongside the existing /health root endpoint for namespace consistency.
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
Preset files are baked into the Docker image at build time — adding/editing presets requires a rebuild. No runtime preset hot-reload. Custom preset has empty param sections which means pipeline code defaults apply — these defaults aren't documented in the preset file itself.
|
||||||
|
|
||||||
|
## Follow-ups
|
||||||
|
|
||||||
|
Consider adding preset validation on load (schema check). Consider documenting pipeline defaults in the custom preset for discoverability. Future M002/M003 work will consume the engine container as a service.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `engine/presets/sign.json` — Sign preset — aggressive simplification for signage vectorization
|
||||||
|
- `engine/presets/patch.json` — Patch preset — smooth curves with auto-close for embroidery
|
||||||
|
- `engine/presets/stencil.json` — Stencil preset — heavy simplification with fixed threshold
|
||||||
|
- `engine/presets/detailed.json` — Detailed preset — max fidelity for illustrations
|
||||||
|
- `engine/presets/custom.json` — Custom preset — empty defaults, user controls everything
|
||||||
|
- `engine/presets/loader.py` — Preset loader with caching, listing, and param merge resolution
|
||||||
|
- `engine/api/routes.py` — Added GET /engine/presets, GET /engine/health, wired preset into /engine/trace
|
||||||
|
- `engine/tests/test_presets.py` — 28 tests covering loader, resolution, endpoint, integration, cross-preset differentiation
|
||||||
|
- `docker/Dockerfile.engine` — Multi-stage Dockerfile: builder compiles pypotrace, runtime uses slim image with engine source only
|
||||||
|
- `.dockerignore` — Excludes .git, .venv, __pycache__, .gsd, node_modules from Docker build context
|
||||||
114
.gsd/milestones/M001/slices/S03/S03-UAT.md
Normal file
114
.gsd/milestones/M001/slices/S03/S03-UAT.md
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
# S03: Preset System + Engine Docker Packaging — UAT
|
||||||
|
|
||||||
|
**Milestone:** M001
|
||||||
|
**Written:** 2026-03-26T04:52:09.425Z
|
||||||
|
|
||||||
|
# S03: Preset System + Engine Docker Packaging — UAT
|
||||||
|
|
||||||
|
**Milestone:** M001
|
||||||
|
**Written:** 2026-03-26
|
||||||
|
|
||||||
|
## UAT Type
|
||||||
|
|
||||||
|
- UAT mode: mixed (artifact-driven for presets, live-runtime for Docker)
|
||||||
|
- Why this mode is sufficient: Preset system is config + API — tests validate behavior. Docker packaging requires live container verification.
|
||||||
|
|
||||||
|
## Preconditions
|
||||||
|
|
||||||
|
- Engine venv exists at `engine/.venv/` with all deps installed
|
||||||
|
- Docker daemon running
|
||||||
|
- Port 8100 available for test container
|
||||||
|
- Working directory is project root (`/home/aux/development/xpltdco/kerf-engine`)
|
||||||
|
|
||||||
|
## Smoke Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd engine && .venv/bin/python -m pytest tests/ -v -k preset && echo "SMOKE OK"
|
||||||
|
```
|
||||||
|
Expected: 28 tests pass, exit 0.
|
||||||
|
|
||||||
|
## Test Cases
|
||||||
|
|
||||||
|
### 1. All 5 presets load and are returned by GET endpoint
|
||||||
|
|
||||||
|
1. Start engine: `cd engine && .venv/bin/python -m uvicorn main:app --port 8099 &`
|
||||||
|
2. `curl -s http://localhost:8099/engine/presets | python3 -m json.tool`
|
||||||
|
3. **Expected:** JSON with keys: sign, patch, stencil, detailed, custom. Each has name, description, preprocessing, vectorization, postprocessing sections.
|
||||||
|
|
||||||
|
### 2. Default preset is 'sign' when none specified
|
||||||
|
|
||||||
|
1. POST a PNG to `/engine/trace` without specifying `preset`
|
||||||
|
2. **Expected:** Response uses sign preset defaults (potrace mode, alphamax=0.8, opttolerance=0.3)
|
||||||
|
|
||||||
|
### 3. Different presets produce different output from same input
|
||||||
|
|
||||||
|
1. POST the same PNG to `/engine/trace` with preset=sign
|
||||||
|
2. POST the same PNG to `/engine/trace` with preset=detailed
|
||||||
|
3. **Expected:** SVG outputs differ — sign produces fewer nodes (aggressive simplification) vs detailed (max fidelity)
|
||||||
|
|
||||||
|
### 4. User params override preset defaults
|
||||||
|
|
||||||
|
1. POST to `/engine/trace` with preset=sign and params={"threshold_manual": 200}
|
||||||
|
2. **Expected:** Response uses threshold_manual=200 (user override) but other params from sign preset
|
||||||
|
|
||||||
|
### 5. Unknown preset is rejected
|
||||||
|
|
||||||
|
1. POST to `/engine/trace` with preset=nonexistent
|
||||||
|
2. **Expected:** 400 error with message about invalid preset
|
||||||
|
|
||||||
|
### 6. Docker image builds and starts healthy
|
||||||
|
|
||||||
|
1. `docker build -f docker/Dockerfile.engine -t kerf-engine:dev .`
|
||||||
|
2. `docker run --rm -d --name kerf-engine-uat -p 8100:8000 kerf-engine:dev`
|
||||||
|
3. `sleep 6`
|
||||||
|
4. `curl -sf http://localhost:8100/engine/health`
|
||||||
|
5. **Expected:** Returns `{"status":"ok"}`
|
||||||
|
6. `docker inspect kerf-engine-uat --format='{{json .State.Health.Status}}'`
|
||||||
|
7. **Expected:** Returns `"healthy"`
|
||||||
|
8. `docker stop kerf-engine-uat`
|
||||||
|
|
||||||
|
### 7. Engine container serves presets endpoint
|
||||||
|
|
||||||
|
1. Start container as in test 6
|
||||||
|
2. `curl -sf http://localhost:8100/engine/presets | python3 -m json.tool`
|
||||||
|
3. **Expected:** Same 5 presets as local endpoint test
|
||||||
|
4. `docker stop kerf-engine-uat`
|
||||||
|
|
||||||
|
### 8. Engine image contains no App code
|
||||||
|
|
||||||
|
1. `docker run --rm kerf-engine:dev ls /app/`
|
||||||
|
2. **Expected:** Only engine files: main.py, api/, pipeline/, output/, presets/. No app/, frontend/, or node_modules/.
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
### Custom preset with no user params uses pipeline defaults
|
||||||
|
|
||||||
|
1. POST to `/engine/trace` with preset=custom and no params
|
||||||
|
2. **Expected:** Succeeds using pipeline code defaults (potrace mode from custom.json vectorization section)
|
||||||
|
|
||||||
|
### Preset with mode override
|
||||||
|
|
||||||
|
1. POST to `/engine/trace` with preset=sign (which defaults to potrace) and mode=vtracer
|
||||||
|
2. **Expected:** Uses vtracer mode but sign preset's preprocessing/postprocessing params
|
||||||
|
|
||||||
|
## Failure Signals
|
||||||
|
|
||||||
|
- Any preset test fails (28 tests must pass)
|
||||||
|
- Docker build fails (missing system deps, pip install errors)
|
||||||
|
- Container starts but /engine/health returns non-200
|
||||||
|
- Docker health status shows "unhealthy"
|
||||||
|
- /engine/presets returns fewer than 5 presets
|
||||||
|
- Same preset applied to different images produces identical SVG (preset not taking effect)
|
||||||
|
|
||||||
|
## Not Proven By This UAT
|
||||||
|
|
||||||
|
- Preset quality tuning (whether sign preset actually produces good signage output — that requires human visual evaluation)
|
||||||
|
- Container performance under load
|
||||||
|
- Container image size optimization
|
||||||
|
- Production deployment (compose, networking, volumes)
|
||||||
|
|
||||||
|
## Notes for Tester
|
||||||
|
|
||||||
|
- The ezdxf deprecation warnings in test output are harmless (L004) — ignore them
|
||||||
|
- Docker build uses cached layers after first build — subsequent builds are fast
|
||||||
|
- Port 8100 is used for Docker tests to avoid conflicting with local dev on 8000
|
||||||
48
.gsd/milestones/M001/slices/S03/tasks/T02-VERIFY.json
Normal file
48
.gsd/milestones/M001/slices/S03/tasks/T02-VERIFY.json
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T02",
|
||||||
|
"unitId": "M001/S03/T02",
|
||||||
|
"timestamp": 1774500578125,
|
||||||
|
"passed": false,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "cd /vmPool/r/repos/xpltdco/kerf",
|
||||||
|
"exitCode": 2,
|
||||||
|
"durationMs": 6,
|
||||||
|
"verdict": "fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "docker build -f docker/Dockerfile.engine -t kerf-engine:dev .",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 1199,
|
||||||
|
"verdict": "pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "docker run --rm -d --name kerf-engine-test -p 8100:8000 kerf-engine:dev",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 425,
|
||||||
|
"verdict": "pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sleep 5",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5007,
|
||||||
|
"verdict": "pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "curl -s http://localhost:8100/engine/health",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 14,
|
||||||
|
"verdict": "pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "docker stop kerf-engine-test",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 567,
|
||||||
|
"verdict": "pass"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"retryAttempt": 1,
|
||||||
|
"maxRetries": 2
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue