From c3c5a9b082c489f1bbc94d5752bc8acd3a29ef47 Mon Sep 17 00:00:00 2001 From: jlightner Date: Thu, 26 Mar 2026 04:57:16 +0000 Subject: [PATCH] chore: auto-commit after complete-milestone GSD-Unit: M001 --- .gsd/DECISIONS.md | 3 +- .gsd/KNOWLEDGE.md | 5 + .gsd/event-log.jsonl | 2 + .gsd/milestones/M001/M001-ROADMAP.md | 2 +- .gsd/milestones/M001/M001-SUMMARY.md | 94 +++++++++++++++ .gsd/milestones/M001/M001-VALIDATION.md | 92 ++++++++++++++ .../milestones/M001/slices/S03/S03-SUMMARY.md | 106 ++++++++++++++++ .gsd/milestones/M001/slices/S03/S03-UAT.md | 114 ++++++++++++++++++ .../M001/slices/S03/tasks/T02-VERIFY.json | 48 ++++++++ .gsd/state-manifest.json | 26 ++-- 10 files changed, 483 insertions(+), 9 deletions(-) create mode 100644 .gsd/milestones/M001/M001-SUMMARY.md create mode 100644 .gsd/milestones/M001/M001-VALIDATION.md create mode 100644 .gsd/milestones/M001/slices/S03/S03-SUMMARY.md create mode 100644 .gsd/milestones/M001/slices/S03/S03-UAT.md create mode 100644 .gsd/milestones/M001/slices/S03/tasks/T02-VERIFY.json diff --git a/.gsd/DECISIONS.md b/.gsd/DECISIONS.md index a7510ed..8181d53 100644 --- a/.gsd/DECISIONS.md +++ b/.gsd/DECISIONS.md @@ -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 | | 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 | diff --git a/.gsd/KNOWLEDGE.md b/.gsd/KNOWLEDGE.md index f87b15c..81a67cb 100644 --- a/.gsd/KNOWLEDGE.md +++ b/.gsd/KNOWLEDGE.md @@ -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. | | 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 @@ -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 | | 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 | +| 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 | diff --git a/.gsd/event-log.jsonl b/.gsd/event-log.jsonl index 4c7b991..442e477 100644 --- a/.gsd/event-log.jsonl +++ b/.gsd/event-log.jsonl @@ -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-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-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"} diff --git a/.gsd/milestones/M001/M001-ROADMAP.md b/.gsd/milestones/M001/M001-ROADMAP.md index c60dd9a..5d0ef18 100644 --- a/.gsd/milestones/M001/M001-ROADMAP.md +++ b/.gsd/milestones/M001/M001-ROADMAP.md @@ -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 | | 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 | diff --git a/.gsd/milestones/M001/M001-SUMMARY.md b/.gsd/milestones/M001/M001-SUMMARY.md new file mode 100644 index 0000000..9b6403d --- /dev/null +++ b/.gsd/milestones/M001/M001-SUMMARY.md @@ -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. diff --git a/.gsd/milestones/M001/M001-VALIDATION.md b/.gsd/milestones/M001/M001-VALIDATION.md new file mode 100644 index 0000000..ef7a374 --- /dev/null +++ b/.gsd/milestones/M001/M001-VALIDATION.md @@ -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. diff --git a/.gsd/milestones/M001/slices/S03/S03-SUMMARY.md b/.gsd/milestones/M001/slices/S03/S03-SUMMARY.md new file mode 100644 index 0000000..70240df --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/S03-SUMMARY.md @@ -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 diff --git a/.gsd/milestones/M001/slices/S03/S03-UAT.md b/.gsd/milestones/M001/slices/S03/S03-UAT.md new file mode 100644 index 0000000..82e2127 --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/S03-UAT.md @@ -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 diff --git a/.gsd/milestones/M001/slices/S03/tasks/T02-VERIFY.json b/.gsd/milestones/M001/slices/S03/tasks/T02-VERIFY.json new file mode 100644 index 0000000..ad5cbc8 --- /dev/null +++ b/.gsd/milestones/M001/slices/S03/tasks/T02-VERIFY.json @@ -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 +} diff --git a/.gsd/state-manifest.json b/.gsd/state-manifest.json index 34a5506..a4474d7 100644 --- a/.gsd/state-manifest.json +++ b/.gsd/state-manifest.json @@ -1,14 +1,14 @@ { "version": 1, - "exported_at": "2026-03-26T04:49:33.565Z", + "exported_at": "2026-03-26T04:56:43.003Z", "milestones": [ { "id": "M001", "title": "Kerf Engine — Raster-to-Vector Pipeline & API", - "status": "active", + "status": "complete", "depends_on": [], "created_at": "2026-03-26T03:52:29.269Z", - "completed_at": null, + "completed_at": "2026-03-26T04:56:42.986Z", "vision": "Build and validate the standalone Kerf Engine: a stateless HTTP API that accepts raster images (or existing SVGs) and returns clean, production-quality vector output (SVG, DXF, JSON) through a multi-stage pipeline of preprocessing, vectorization, post-processing, and format conversion. This is the proprietary IP core — it must work flawlessly before anything else is built.", "success_criteria": [ "Clean high-contrast PNG logo → SVG renders correctly in browser", @@ -250,16 +250,16 @@ "milestone_id": "M001", "id": "S03", "title": "Preset System + Engine Docker Packaging", - "status": "pending", + "status": "complete", "risk": "low — presets are config files; Docker packaging is well-understood", "depends": [ "S02" ], "demo": "GET /engine/presets returns all presets; each preset produces distinct output from same input; engine runs in Docker", "created_at": "2026-03-26T03:52:29.269Z", - "completed_at": null, - "full_summary_md": "", - "full_uat_md": "", + "completed_at": "2026-03-26T04:52:09.415Z", + "full_summary_md": "---\nid: S03\nparent: M001\nmilestone: M001\nprovides:\n - Preset system with 5 tuned configs and merge-based param resolution\n - GET /engine/presets endpoint\n - Docker image kerf-engine:dev with healthcheck\n - GET /engine/health endpoint\nrequires:\n - slice: S02\n provides: Post-processing pipeline and output format generators (SVG, DXF, JSON) consumed by preset-driven trace flow\naffects:\n []\nkey_files:\n - engine/presets/sign.json\n - engine/presets/patch.json\n - engine/presets/stencil.json\n - engine/presets/detailed.json\n - engine/presets/custom.json\n - engine/presets/loader.py\n - engine/api/routes.py\n - engine/tests/test_presets.py\n - docker/Dockerfile.engine\n - .dockerignore\nkey_decisions:\n - Preset default is 'sign' — covers the most common use case\n - Presets use flat JSON with three sections (preprocessing, vectorization, postprocessing)\n - resolve_params merges preset → user_params with user taking precedence\n - Custom preset has empty param sections so pipeline defaults apply unless user provides overrides\n - Multi-stage Docker build separates build deps from runtime (smaller image, no compiler tools)\n - Engine image contains only engine source — enforces Engine/App separation (D001)\n - Health endpoint at /engine/health for namespace consistency with other /engine/* routes\npatterns_established:\n - Preset-driven pipeline configuration: presets define defaults, user params override\n - Multi-stage Docker build pattern for Python+C extension packages (pypotrace)\n - Dual health endpoints: /health (root) for simple checks, /engine/health (namespaced) for Docker/orchestration\nobservability_surfaces:\n - GET /engine/health — container health probe endpoint\n - GET /engine/presets — returns all available presets with full config (useful for debugging preset state)\n - Docker HEALTHCHECK — automatic container health monitoring (15s interval, 3 retries)\ndrill_down_paths:\n - .gsd/milestones/M001/slices/S03/tasks/T01-SUMMARY.md\n - .gsd/milestones/M001/slices/S03/tasks/T02-SUMMARY.md\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-03-26T04:52:09.425Z\nblocker_discovered: false\n---\n\n# S03: Preset System + Engine Docker Packaging\n\n**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.**\n\n## What Happened\n\nThis slice wrapped up the M001 engine milestone with two deliverables: a preset system and Docker packaging.\n\n**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.\n\n**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.\n\n## Verification\n\nAll 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.\n\n## Requirements Advanced\n\nNone.\n\n## Requirements Validated\n\nNone.\n\n## New Requirements Surfaced\n\nNone.\n\n## Requirements Invalidated or Re-scoped\n\nNone.\n\n## Deviations\n\nT02 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.\n\n## Known Limitations\n\nPreset 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.\n\n## Follow-ups\n\nConsider 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.\n\n## Files Created/Modified\n\n- `engine/presets/sign.json` — Sign preset — aggressive simplification for signage vectorization\n- `engine/presets/patch.json` — Patch preset — smooth curves with auto-close for embroidery\n- `engine/presets/stencil.json` — Stencil preset — heavy simplification with fixed threshold\n- `engine/presets/detailed.json` — Detailed preset — max fidelity for illustrations\n- `engine/presets/custom.json` — Custom preset — empty defaults, user controls everything\n- `engine/presets/loader.py` — Preset loader with caching, listing, and param merge resolution\n- `engine/api/routes.py` — Added GET /engine/presets, GET /engine/health, wired preset into /engine/trace\n- `engine/tests/test_presets.py` — 28 tests covering loader, resolution, endpoint, integration, cross-preset differentiation\n- `docker/Dockerfile.engine` — Multi-stage Dockerfile: builder compiles pypotrace, runtime uses slim image with engine source only\n- `.dockerignore` — Excludes .git, .venv, __pycache__, .gsd, node_modules from Docker build context\n", + "full_uat_md": "# S03: Preset System + Engine Docker Packaging — UAT\n\n**Milestone:** M001\n**Written:** 2026-03-26T04:52:09.425Z\n\n# S03: Preset System + Engine Docker Packaging — UAT\n\n**Milestone:** M001\n**Written:** 2026-03-26\n\n## UAT Type\n\n- UAT mode: mixed (artifact-driven for presets, live-runtime for Docker)\n- Why this mode is sufficient: Preset system is config + API — tests validate behavior. Docker packaging requires live container verification.\n\n## Preconditions\n\n- Engine venv exists at `engine/.venv/` with all deps installed\n- Docker daemon running\n- Port 8100 available for test container\n- Working directory is project root (`/home/aux/development/xpltdco/kerf-engine`)\n\n## Smoke Test\n\n```bash\ncd engine && .venv/bin/python -m pytest tests/ -v -k preset && echo \"SMOKE OK\"\n```\nExpected: 28 tests pass, exit 0.\n\n## Test Cases\n\n### 1. All 5 presets load and are returned by GET endpoint\n\n1. Start engine: `cd engine && .venv/bin/python -m uvicorn main:app --port 8099 &`\n2. `curl -s http://localhost:8099/engine/presets | python3 -m json.tool`\n3. **Expected:** JSON with keys: sign, patch, stencil, detailed, custom. Each has name, description, preprocessing, vectorization, postprocessing sections.\n\n### 2. Default preset is 'sign' when none specified\n\n1. POST a PNG to `/engine/trace` without specifying `preset`\n2. **Expected:** Response uses sign preset defaults (potrace mode, alphamax=0.8, opttolerance=0.3)\n\n### 3. Different presets produce different output from same input\n\n1. POST the same PNG to `/engine/trace` with preset=sign\n2. POST the same PNG to `/engine/trace` with preset=detailed\n3. **Expected:** SVG outputs differ — sign produces fewer nodes (aggressive simplification) vs detailed (max fidelity)\n\n### 4. User params override preset defaults\n\n1. POST to `/engine/trace` with preset=sign and params={\"threshold_manual\": 200}\n2. **Expected:** Response uses threshold_manual=200 (user override) but other params from sign preset\n\n### 5. Unknown preset is rejected\n\n1. POST to `/engine/trace` with preset=nonexistent\n2. **Expected:** 400 error with message about invalid preset\n\n### 6. Docker image builds and starts healthy\n\n1. `docker build -f docker/Dockerfile.engine -t kerf-engine:dev .`\n2. `docker run --rm -d --name kerf-engine-uat -p 8100:8000 kerf-engine:dev`\n3. `sleep 6`\n4. `curl -sf http://localhost:8100/engine/health`\n5. **Expected:** Returns `{\"status\":\"ok\"}`\n6. `docker inspect kerf-engine-uat --format='{{json .State.Health.Status}}'`\n7. **Expected:** Returns `\"healthy\"`\n8. `docker stop kerf-engine-uat`\n\n### 7. Engine container serves presets endpoint\n\n1. Start container as in test 6\n2. `curl -sf http://localhost:8100/engine/presets | python3 -m json.tool`\n3. **Expected:** Same 5 presets as local endpoint test\n4. `docker stop kerf-engine-uat`\n\n### 8. Engine image contains no App code\n\n1. `docker run --rm kerf-engine:dev ls /app/`\n2. **Expected:** Only engine files: main.py, api/, pipeline/, output/, presets/. No app/, frontend/, or node_modules/.\n\n## Edge Cases\n\n### Custom preset with no user params uses pipeline defaults\n\n1. POST to `/engine/trace` with preset=custom and no params\n2. **Expected:** Succeeds using pipeline code defaults (potrace mode from custom.json vectorization section)\n\n### Preset with mode override\n\n1. POST to `/engine/trace` with preset=sign (which defaults to potrace) and mode=vtracer\n2. **Expected:** Uses vtracer mode but sign preset's preprocessing/postprocessing params\n\n## Failure Signals\n\n- Any preset test fails (28 tests must pass)\n- Docker build fails (missing system deps, pip install errors)\n- Container starts but /engine/health returns non-200\n- Docker health status shows \"unhealthy\"\n- /engine/presets returns fewer than 5 presets\n- Same preset applied to different images produces identical SVG (preset not taking effect)\n\n## Not Proven By This UAT\n\n- Preset quality tuning (whether sign preset actually produces good signage output — that requires human visual evaluation)\n- Container performance under load\n- Container image size optimization\n- Production deployment (compose, networking, volumes)\n\n## Notes for Tester\n\n- The ezdxf deprecation warnings in test output are harmless (L004) — ignore them\n- Docker build uses cached layers after first build — subsequent builds are fast\n- Port 8100 is used for Docker tests to avoid conflicting with local dev on 8000\n", "goal": "Implement preset system (sign, patch, stencil, detailed, custom) as JSON config files and package engine as standalone Docker container with healthcheck.", "success_criteria": "- All 5 presets defined as JSON files in /engine/presets/\\n- GET /engine/presets returns complete preset list with parameter values\\n- Preset selection in /engine/trace produces meaningfully different output\\n- Engine Dockerfile builds and runs\\n- Healthcheck endpoint passes\\n- Engine deployable independently (no App dependency)", "proof_level": "integration + operational — Docker container starts, healthcheck passes, presets work", @@ -849,6 +849,18 @@ "revisable": "No", "made_by": "agent", "superseded_by": null + }, + { + "seq": 12, + "id": "D004", + "when_context": "", + "scope": "architecture", + "decision": "Engine Docker image build strategy", + "choice": "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.", + "rationale": "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.", + "revisable": "Yes", + "made_by": "agent", + "superseded_by": null } ], "verification_evidence": [