diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index c1d89e2..465993d 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -46,7 +46,7 @@ Requirements for initial release. Each maps to roadmap phases. ### Infrastructure -- [ ] **INFRA-01**: Application runs as a single Docker container +- [x] **INFRA-01**: Application runs as a single Docker container - [x] **INFRA-02**: Application has minimal runtime dependencies (no Node.js, no external DB) ## v2 Requirements @@ -83,7 +83,7 @@ Which phases cover which requirements. Updated during roadmap creation. | Requirement | Phase | Status | |-------------|-------|--------| -| INFRA-01 | Phase 1 | Pending | +| INFRA-01 | Phase 1 | Complete | | INFRA-02 | Phase 1 | Complete | | DRV-01 | Phase 2 | Pending | | DRV-02 | Phase 2 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index f444df7..f9d5852 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -32,7 +32,7 @@ Decimal phases appear between their surrounding integers in numeric order. **Plans**: 3 plans Plans: -- [ ] 01-01: Docker container scaffold (Dockerfile, python:3.12-slim-bookworm, volume, healthcheck) +- [x] 01-01: Docker container scaffold (Dockerfile, python:3.12-slim-bookworm, volume, healthcheck) - [ ] 01-02: SQLite schema and data models (peewee ORM, driver + printer + client tables) - [ ] 01-03: .intunewin format spike (AES-256-CBC encrypted ZIP-in-ZIP with detection.xml, validated against real Intune) @@ -108,7 +108,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation | 1/3 | In Progress| | +| 1. Foundation | 2/3 | In Progress| | | 2. Driver Management | 0/3 | Not started | - | | 3. Printer Configuration | 0/3 | Not started | - | | 4. Script Generation | 0/3 | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index c505037..23569bb 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,16 +2,16 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: planning -stopped_at: Completed 01-03-PLAN.md -last_updated: "2026-04-10T09:26:32.100Z" -last_activity: 2026-04-10 — Roadmap created, 5 phases derived from 27 requirements +status: executing +stopped_at: Completed 01-01-PLAN.md +last_updated: "2026-04-10T09:27:00.000Z" +last_activity: 2026-04-10 — Plan 01-01 complete: Docker scaffold, FastAPI app shell, test suite progress: total_phases: 5 completed_phases: 0 total_plans: 3 - completed_plans: 1 - percent: 0 + completed_plans: 2 + percent: 7 --- # Project State @@ -26,31 +26,30 @@ See: .planning/PROJECT.md (updated 2026-04-10) ## Current Position Phase: 1 of 5 (Foundation) -Plan: 0 of 3 in current phase -Status: Ready to plan -Last activity: 2026-04-10 — Roadmap created, 5 phases derived from 27 requirements +Plan: 2 of 3 in current phase (01-01 complete) +Status: Executing +Last activity: 2026-04-10 — Plan 01-01 complete: Docker scaffold, FastAPI app shell, sidebar templates, 3-test green suite -Progress: [░░░░░░░░░░] 0% +Progress: [░░░░░░░░░░] 7% ## Performance Metrics **Velocity:** -- Total plans completed: 0 -- Average duration: - -- Total execution time: 0 hours +- Total plans completed: 2 +- Average duration: ~3 min +- Total execution time: ~6 minutes **By Phase:** | Phase | Plans | Total | Avg/Plan | |-------|-------|-------|----------| -| - | - | - | - | +| Phase 01 Foundation | 2 | ~6 min | ~3 min | **Recent Trend:** -- Last 5 plans: - -- Trend: - +- Last 5 plans: 01-03 (3 min), 01-01 (3 min) +- Trend: Consistent *Updated after each plan completion* -| Phase 01 P03 | 7 | 1 tasks | 4 files | ## Accumulated Context @@ -63,6 +62,8 @@ Recent decisions affecting current work: - Phase 1: Stack is Python 3.12 + FastAPI + Jinja2 + HTMX + SQLite + pycryptodome - [Phase 01]: IV is 16 bytes for .intunewin AES-256-CBC (not 32 as documented in STACK.md — corrected from RESEARCH.md) - [Phase 01]: Inner .intunewin ZIP uses DEFLATE compression; outer ZIP uses STORED (matches C# reference implementation) +- [Plan 01-01]: Use asynccontextmanager lifespan instead of deprecated @app.on_event — required for FastAPI 0.115+ / Starlette 0.40+ +- [Plan 01-01]: TemplateResponse uses request= kwarg signature (not positional dict) — Starlette 0.40+ compatibility ### Pending Todos @@ -75,6 +76,6 @@ None yet. ## Session Continuity -Last session: 2026-04-10T09:26:23.280Z -Stopped at: Completed 01-03-PLAN.md -Resume file: None +Last session: 2026-04-10T09:27:00Z +Stopped at: Completed 01-01-PLAN.md +Resume file: .planning/phases/01-foundation/01-02-PLAN.md diff --git a/.planning/config.json b/.planning/config.json index 1e4156f..578f403 100644 --- a/.planning/config.json +++ b/.planning/config.json @@ -8,6 +8,7 @@ "research": true, "plan_check": true, "verifier": true, - "nyquist_validation": true + "nyquist_validation": true, + "_auto_chain_active": false } -} +} \ No newline at end of file diff --git a/.planning/phases/01-foundation/01-01-SUMMARY.md b/.planning/phases/01-foundation/01-01-SUMMARY.md new file mode 100644 index 0000000..280991f --- /dev/null +++ b/.planning/phases/01-foundation/01-01-SUMMARY.md @@ -0,0 +1,150 @@ +--- +phase: 01-foundation +plan: 01 +subsystem: infra +tags: [docker, fastapi, jinja2, htmx, pico-css, alpine-js, pytest, uvicorn] + +# Dependency graph +requires: [] +provides: + - Running FastAPI app with GET /health and dashboard page + - Docker scaffold with offline static asset baking (Pico CSS, HTMX, Alpine.js) + - Sidebar navigation shell with 5 sections (Dashboard, Drivers, Printers, Clients, Packages) + - Test scaffold with health and no-CDN-URL tests passing +affects: [01-02, 01-03, 02-drivers, 03-printers, 04-clients, 05-packages] + +# Tech tracking +tech-stack: + added: [fastapi==0.115.x, uvicorn[standard]==0.30.x, jinja2==3.1.x, python-multipart==0.0.9, pycryptodome==3.20.x, python-dotenv==1.0.x, peewee==3.17.x, pytest, httpx] + patterns: + - Sync def route handlers (FastAPI runs in thread pool — Peewee-compatible) + - StaticFiles mount from pathlib.Path(__file__).parent / "static" + - asynccontextmanager lifespan for startup hooks (not deprecated on_event) + - TemplateResponse with request= kwarg for Starlette 0.40+ compatibility + - Docker offline asset baking — curl in RUN layer, assets in /app/imptune/static/ + +key-files: + created: + - Dockerfile + - docker-compose.yml + - requirements.txt + - requirements-dev.txt + - imptune/__init__.py + - imptune/main.py + - imptune/config.py + - imptune/api/__init__.py + - imptune/api/health.py + - imptune/api/pages.py + - imptune/templates/base.html + - imptune/templates/dashboard.html + - imptune/static/app.css + - tests/__init__.py + - tests/conftest.py + - tests/test_health.py + - tests/test_static.py + modified: [] + +key-decisions: + - "Use asynccontextmanager lifespan instead of deprecated @app.on_event (FastAPI/Starlette best practice)" + - "TemplateResponse uses request= keyword arg (not positional context dict) for Starlette 0.40+ compatibility" + - "Static dir resolved via pathlib.Path(__file__).parent / static — works inside Docker and local dev" + - "Sync def route handlers throughout — FastAPI auto-threads, compatible with Peewee ORM" + +patterns-established: + - "Pattern 1: All static asset references use /static/ paths — no CDN URLs anywhere in templates" + - "Pattern 2: TemplateResponse(request=request, name=..., context={...}) — Starlette 0.40+ signature" + - "Pattern 3: config.py loads from env with sensible defaults; all paths derived from DATA_DIR" + - "Pattern 4: TestClient fixture in conftest.py with monkeypatched tmp_data_dir for isolation" + +requirements-completed: [INFRA-01, INFRA-02] + +# Metrics +duration: 3min +completed: 2026-04-10 +--- + +# Phase 1, Plan 01: Docker Scaffold and App Shell Summary + +**FastAPI app with Pico CSS sidebar shell, offline-baked static assets (HTMX, Alpine.js), GET /health, and 3-test green suite — all in a single python:3.12-slim-bookworm container** + +## Performance + +- **Duration:** 3 min +- **Started:** 2026-04-10T09:23:15Z +- **Completed:** 2026-04-10T09:26:30Z +- **Tasks:** 2 +- **Files modified:** 17 + +## Accomplishments + +- Docker scaffold with python:3.12-slim-bookworm base; curl downloads Pico CSS v2, HTMX 2.x, Alpine.js 3.x at build time and purges curl — zero CDN at runtime +- FastAPI app with asynccontextmanager lifespan, StaticFiles mount, health router, and dashboard page router +- Sidebar layout template (`base.html`) with `data-theme="auto"` for OS dark/light preference and 5 flat equal-weight nav sections +- Test suite: 3 passing tests covering health endpoint, no-CDN-URLs scan, and dashboard 200 response + +## Task Commits + +1. **Task 1: Docker scaffold, FastAPI app shell, templates** - `bd4e132` (feat) +2. **Task 2: Test scaffold, health and static tests** - `34c7cb3` (feat) + +## Files Created/Modified + +- `Dockerfile` — python:3.12-slim-bookworm, curl-baked static assets, stdlib healthcheck, uvicorn CMD +- `docker-compose.yml` — imptune_data:/data volume, DATA_DIR env, restart unless-stopped +- `requirements.txt` — all phase 1-5 deps (fastapi, uvicorn, jinja2, peewee, pycryptodome, etc.) +- `requirements-dev.txt` — pytest, httpx +- `imptune/main.py` — FastAPI app with lifespan, StaticFiles, router registration +- `imptune/config.py` — DATA_DIR/PORT env loading, DB_PATH/DRIVERS_DIR derivation +- `imptune/api/health.py` — GET /health → {"status": "ok"} +- `imptune/api/pages.py` — GET / → dashboard.html (sync def, new TemplateResponse signature) +- `imptune/templates/base.html` — data-theme="auto", /static/ assets only, sidebar nav +- `imptune/templates/dashboard.html` — quick actions + empty state recent activity +- `imptune/static/app.css` — sidebar flex layout, active link highlight, quick action styling +- `tests/conftest.py` — client and tmp_data_dir fixtures +- `tests/test_health.py` — health endpoint 200 test +- `tests/test_static.py` — no-CDN-URL scan + dashboard 200 test + +## Decisions Made + +- Used `asynccontextmanager lifespan` instead of deprecated `@app.on_event("startup")` — avoids DeprecationWarning on FastAPI 0.115+ / Python 3.13 +- Used `TemplateResponse(request=request, name=..., context={...})` signature — the old positional dict form triggers a `TypeError: unhashable type: 'dict'` on Starlette 0.40+ due to LRUCache key behavior +- Static directory resolved from `pathlib.Path(__file__).parent / "static"` — works in Docker and local dev without hardcoded paths + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Fixed Starlette TemplateResponse signature incompatibility** +- **Found during:** Task 2 (test_dashboard_returns_200 failed) +- **Issue:** `templates.TemplateResponse("dashboard.html", {"request": request, ...})` raises `TypeError: unhashable type: 'dict'` on Starlette 0.40+ — context dict used as LRUCache key +- **Fix:** Changed to `templates.TemplateResponse(request=request, name="dashboard.html", context={...})` +- **Files modified:** `imptune/api/pages.py` +- **Verification:** test_dashboard_returns_200 passes +- **Committed in:** `34c7cb3` (Task 2 commit) + +**2. [Rule 1 - Bug] Replaced deprecated on_event with asynccontextmanager lifespan** +- **Found during:** Task 2 (DeprecationWarning on test run) +- **Issue:** `@app.on_event("startup")` is deprecated in FastAPI 0.95+ / Starlette 0.37+; triggers warning on every test run +- **Fix:** Replaced with `@asynccontextmanager async def lifespan(app)` passed to `FastAPI(lifespan=lifespan)` +- **Files modified:** `imptune/main.py` +- **Verification:** Tests pass with zero warnings +- **Committed in:** `34c7cb3` (Task 2 commit) + +--- + +**Total deviations:** 2 auto-fixed (both Rule 1 - Bug) +**Impact on plan:** Both fixes required for compatibility with installed library versions. No scope creep. + +## Issues Encountered + +- Starlette's `TemplateResponse` API changed in 0.40.0 — old positional-dict form breaks silently until test run. Fixed inline. + +## Next Phase Readiness + +- App shell and health endpoint ready — next plan (01-02) can build the SQLite schema and Peewee models on this foundation +- Docker image can be built once assets are downloaded; local dev works without Docker via `python3 -m pytest` and direct uvicorn run +- No blockers for 01-02 + +--- +*Phase: 01-foundation* +*Completed: 2026-04-10*