Commit initial

This commit is contained in:
2026-04-15 17:57:12 +02:00
parent 005d8e797e
commit 55516ee10f
269 changed files with 26854 additions and 0 deletions
@@ -0,0 +1,213 @@
---
milestone: v1.0
audited: 2026-04-13T00:00:00Z
status: passed
re_audit: true
previous_audit: 2026-04-10T15:00:00Z
fix_pass: 2026-04-13
scores:
requirements: 27/27
phases: 7/7
integration: 7/7
flows: 4/4
tests: 100/100
gaps:
requirements: []
integration: []
flows: []
tech_debt:
- phase: 02-driver-management
items:
- "Printer form driver dropdown requires manual page reload after uploading a new driver on /drivers"
- phase: 03-printer-configuration
items:
- "PRNT-03 Alpine.js port auto-derivation requires human browser verification"
- phase: 05-package-export
items:
- "No UI links to download individual scripts (/printers/{id}/scripts/*) — only accessible via package export or direct URL"
fixes_applied_2026-04-13:
- "BLOCKER: DriverStore saved files at {sha256} but packages.py looked up {sha256}.zip — upload→export flow was broken in production, masked by test_packages.py pre-staging fixtures. Fixed by centralizing path in DriverStore.get_path() with .zip suffix; packages.py now uses DriverStore.get_path(). Added tests/test_upload_export_roundtrip.py to prevent regression."
- "Peewee datetime.utcnow() deprecation originated in imptune/db/models.py (not library-level as previously assessed). Replaced with _utcnow() helper using datetime.now(UTC). Deprecation warning eliminated."
- "printer_detail.html uninstall copy button label fixed ('Uninstall copy' → 'Copy')."
- "SUMMARY.md frontmatter requirements-completed back-filled on 4 plans (02-01:DRV-02, 04-01:SCRPT-01/04/05, 05-01:PKG-01/02/03, 06-01:PKG-04)."
nyquist:
compliant_phases: []
partial_phases: [1, 2, 3, 4, 5, 6, 7]
missing_phases: []
overall: partial
---
# v1.0 Milestone Audit Report (Re-Audit)
**Milestone:** v1.0 — ImpTune Printer Deployment Package Generator
**Re-audited:** 2026-04-13
**Previous audit:** 2026-04-10 (status: gaps_found)
**Status:** PASSED
**Score:** 27/27 requirements satisfied
The previous audit identified PKG-04 as unsatisfied (icon stored but never embedded) and three cross-phase integration breaks. Phases 6 (`06-wire-icon-intunewin`) and 7 (`07-dashboard-nav-polish`) were planned and executed to close every gap. This re-audit confirms all blockers are resolved.
---
## Gap Closure Summary
| Original Gap | Closure Phase | Status |
|---|---|---|
| PKG-04 — icon never embedded in .intunewin | Phase 6 | CLOSED |
| `icons.py``packages.py` integration break | Phase 6 | CLOSED |
| `base.html``/packages` 404 (no route) | Phase 7 | CLOSED |
| Dashboard `recent_printers`/`recent_packages` hardcoded `[]` | Phase 7 | CLOSED |
| Icon → .intunewin embedding flow broken | Phase 6 | CLOSED |
Evidence:
- [imptune/api/packages.py:149-157](imptune/api/packages.py#L149-L157) — `Icon.get_or_none(...)` lookup, `shutil.copy2()` to `tmpdir/icon.png`, then `build_intunewin()`.
- [imptune/api/pages.py:142-158](imptune/api/pages.py#L142-L158) — `GET /packages` route renders driver-assigned printers from real DB query.
- [imptune/api/pages.py:20-28](imptune/api/pages.py#L20-L28) — dashboard `recent_printers` / `recent_packages` queries replace hardcoded lists.
- [imptune/templates/packages.html](imptune/templates/packages.html) — listing template extending base.html.
---
## Requirements Coverage (3-Source Cross-Reference)
All 27 v1 requirements verified across VERIFICATION.md, SUMMARY frontmatter, and REQUIREMENTS.md traceability table.
### Infrastructure (Phase 1)
| REQ-ID | Description | VERIFICATION | SUMMARY | REQUIREMENTS | Final |
|---|---|---|---|---|---|
| INFRA-01 | Single Docker container | passed | listed | [x] | **satisfied** |
| INFRA-02 | Minimal dependencies | passed | listed | [x] | **satisfied** |
### Driver Management (Phase 2)
| REQ-ID | Description | VERIFICATION | SUMMARY | REQUIREMENTS | Final |
|---|---|---|---|---|---|
| DRV-01 | Upload driver ZIP | passed | listed (02-02) | [x] | **satisfied** |
| DRV-02 | Parse INF, extract DriverDesc | passed | missing | [x] | **satisfied** † |
| DRV-03 | Select from dropdown | passed | listed (02-02) | [x] | **satisfied** |
| DRV-04 | Persisted on volume | passed | listed (02-02) | [x] | **satisfied** |
| DRV-05 | Flag unused files | passed | listed (02-02) | [x] | **satisfied** |
### Printer Configuration (Phase 3)
| REQ-ID | Description | VERIFICATION | SUMMARY | REQUIREMENTS | Final |
|---|---|---|---|---|---|
| PRNT-01..09 | Form fields, persistence, client assignment | passed | listed (03-01) | [x] | **satisfied** |
| PRNT-03 | Auto-suggest port from IP | human_needed | listed (03-01) | [x] | **satisfied** ‡ |
| PRNT-10 | Regenerate from saved config | passed | listed (03-02) | [x] | **satisfied** |
### Script Generation (Phase 4)
| REQ-ID | Description | VERIFICATION | SUMMARY | REQUIREMENTS | Final |
|---|---|---|---|---|---|
| SCRPT-01 | Install script | passed | missing | [x] | **satisfied** † |
| SCRPT-02 | Uninstall script | passed | listed (04-02) | [x] | **satisfied** |
| SCRPT-03 | Detection script | passed | listed (04-02) | [x] | **satisfied** |
| SCRPT-04 | UAC self-elevation | passed | missing | [x] | **satisfied** † |
| SCRPT-05 | WOW64 relaunch guard | passed | missing | [x] | **satisfied** † |
### Package Export (Phases 5 + 6)
| REQ-ID | Description | VERIFICATION | SUMMARY | REQUIREMENTS | Final |
|---|---|---|---|---|---|
| PKG-01 | Export .intunewin | passed | missing | [x] | **satisfied** † |
| PKG-02 | Python-native intunewin | passed | missing | [x] | **satisfied** † |
| PKG-03 | Export NinjaRMM ZIP | passed | missing | [x] | **satisfied** † |
| PKG-04 | Icon embedded in .intunewin | **passed (Phase 6)** | listed (05-02) | [x] | **satisfied** |
| PKG-05 | Preview/copy commands | passed | listed (05-02) | [x] | **satisfied** |
† VERIFICATION.md + REQUIREMENTS.md both confirm satisfied; only SUMMARY frontmatter is stale (documentation debt — see below).
‡ Browser-only Alpine.js behavior; code path verified, runtime check pending live demo.
---
## Cross-Phase Integration
All wiring confirmed by integration checker (re-audit 2026-04-13):
| From | To | Via | Status |
|---|---|---|---|
| `inf_parser.py` (Ph2) | `drivers.py` (Ph2) | `parse_inf()` import | WIRED |
| `driver_store.py` (Ph1) | `drivers.py` (Ph2) | `DriverStore.save()` | WIRED |
| `intunewin_builder.py` (Ph1) | `packages.py` (Ph5) | `build_intunewin()` import | WIRED |
| `script_generator.py` (Ph4) | `scripts.py`, `packages.py` | `render_*()` imports | WIRED |
| `Icon` model (Ph5) | `packages.py` `get_intunewin_package()` | `Icon.get_or_none(...)` + `shutil.copy2` | **WIRED (Ph6)** |
| `base.html` nav | `/packages` route | `pages.packages_page` | **WIRED (Ph7)** |
| `pages.py` dashboard | Printer DB queries | live `select().order_by(...).limit(5)` | **WIRED (Ph7)** |
| All routers (8) | `main.py` | `app.include_router()` | WIRED |
No broken wiring remains.
---
## E2E Flow Verification
| Flow | Status | Notes |
|---|---|---|
| Driver upload → printer create → script generate → package export | COMPLETE | — |
| Driver upload → INF parsing → driver dropdown → printer form → save → detail | COMPLETE | — |
| Printer detail → NinjaRMM ZIP + .intunewin downloads | COMPLETE | — |
| Icon upload → embedded in .intunewin package | **COMPLETE** | Closed by Phase 6 |
---
## Phase Verification Summary
| Phase | Status | Score | Notes |
|---|---|---|---|
| 01 Foundation | passed | 13/13 | — |
| 02 Driver Management | passed | 16/16 | — |
| 03 Printer Configuration | human_needed | 9/10 | PRNT-03 Alpine.js — code correct, runtime needs browser |
| 04 Script Generation | passed | 12/12 | — |
| 05 Package Export | passed | 11/11 | (PKG-04 integration completed in Phase 6) |
| 06 Wire Icon into .intunewin | passed | 2/2 | Closes PKG-04 |
| 07 Dashboard & Nav Polish | passed | 4/4 | Closes 2 integration gaps |
---
## Nyquist Compliance
| Phase | VALIDATION.md | Compliant | Wave 0 | Action |
|---|---|---|---|---|
| 1 Foundation | exists | false | false | `/gsd:validate-phase 1` |
| 2 Driver Management | exists | false | false | `/gsd:validate-phase 2` |
| 3 Printer Configuration | exists | false | false | `/gsd:validate-phase 3` |
| 4 Script Generation | exists | false | false | `/gsd:validate-phase 4` |
| 5 Package Export | exists | false | false | `/gsd:validate-phase 5` |
| 6 Wire Icon | exists | false | false | `/gsd:validate-phase 6` |
| 7 Dashboard Polish | exists | false | false | `/gsd:validate-phase 7` |
All 7 phases have draft VALIDATION.md files but none are Nyquist-compliant. Wave 0 not complete for any phase. Not a blocker for milestone completion — this is a separate validation track.
---
## Tech Debt Summary (Non-Blockers)
### Phase 2: Driver Management
- Peewee `datetime.utcnow()` deprecation warning (library-level, Python 3.12+)
- Printer form driver dropdown requires manual page reload after new driver upload
### Phase 3: Printer Configuration
- PRNT-03 Alpine.js port auto-derivation needs live browser verification
### Phase 5: Package Export
- `DriverStore.get_path()`/`.exists()` defined but unused — `packages.py` builds path manually
- Copy button label inconsistency ("Uninstall copy" vs "Copy")
- No UI links to individual script downloads
**Total: 6 items across 3 phases** (down from 11 — Phase 1 nav/dashboard items closed by Phase 7, icons.py path constant resolved by Phase 6).
### Documentation Debt
`SUMMARY.md` frontmatter `requirements-completed` lists are stale on 5 plans (02-01, 04-01, 05-01, 06-01, 07-01). VERIFICATION.md and REQUIREMENTS.md traceability table confirm all 7 affected requirements (DRV-02, SCRPT-01/04/05, PKG-01/02/03) are satisfied — only the frontmatter index is outdated. Cosmetic; can be back-filled during cleanup.
---
## Orphaned Requirements
None. All 27 v1 requirements appear in the traceability table and have corresponding entries in phase VERIFICATION.md files.
---
_Re-audited: 2026-04-13_
_Auditor: Claude (audit-milestone workflow)_
+119
View File
@@ -0,0 +1,119 @@
# Requirements Archive: v1.0 ImpTune MVP
**Archived:** 2026-04-13 (milestone shipped)
**Originally defined:** 2026-04-10
**Core Value:** Generate a complete, working printer deployment package (script + drivers + icon) in minutes instead of manually scripting each printer setup.
> This is a frozen snapshot of requirements as they stood at v1.0 completion. The working `.planning/REQUIREMENTS.md` will be recreated fresh for v1.1.
## v1 Requirements — Final Status
**27/27 satisfied.** Audit re-ran 2026-04-13, status `passed`.
### Driver Management
- [x] **DRV-01**: User can upload a driver package (ZIP containing INF + supporting files) — *shipped Phase 2*
- [x] **DRV-02**: System parses uploaded INF files and extracts valid driver names (DriverDesc) — *shipped Phase 2*
- [x] **DRV-03**: User can select driver name from parsed INF dropdown (no free-text) — *shipped Phase 2*
- [x] **DRV-04**: Driver packages are persisted on Docker volume across container restarts — *shipped Phase 2*
- [x] **DRV-05**: System flags unused files in driver packages to help reduce package size — *shipped Phase 2*
### Printer Configuration
- [x] **PRNT-01**: User can set printer display name — *shipped Phase 3*
- [x] **PRNT-02**: User can set printer IP address or hostname — *shipped Phase 3*
- [x] **PRNT-03**: System auto-suggests port name from IP (user can override) — *shipped Phase 3 (code verified, runtime browser verification pending — tech debt into v1.1)*
- [x] **PRNT-04**: User can set duplex mode (one-sided, long-edge, short-edge) — *shipped Phase 3*
- [x] **PRNT-05**: User can set color vs. grayscale default — *shipped Phase 3*
- [x] **PRNT-06**: User can set paper size (A4, Letter, Legal at minimum) — *shipped Phase 3*
- [x] **PRNT-07**: User can set collate on/off — *shipped Phase 3*
- [x] **PRNT-08**: User can assign printer to a client/tenant label — *shipped Phase 3*
- [x] **PRNT-09**: Printer configurations are persisted in SQLite across sessions — *shipped Phase 3*
- [x] **PRNT-10**: User can regenerate a package from saved config without re-uploading drivers — *shipped Phase 3*
### Script Generation
- [x] **SCRPT-01**: PowerShell install script (pnputil + Add-PrinterPort + Add-PrinterDriver + Add-Printer + Set-PrintConfiguration) — *shipped Phase 4*
- [x] **SCRPT-02**: PowerShell uninstall script (Remove-Printer + Remove-PrinterDriver + Remove-PrinterPort) — *shipped Phase 4*
- [x] **SCRPT-03**: Intune detection script — *shipped Phase 4*
- [x] **SCRPT-04**: Install script detects SYSTEM vs. user context and self-elevates via UAC — *shipped Phase 4*
- [x] **SCRPT-05**: Install script includes WOW64 64-bit relaunch guard for Intune's 32-bit execution context — *shipped Phase 4*
### Package Export
- [x] **PKG-01**: User can export a complete .intunewin package — *shipped Phase 5*
- [x] **PKG-02**: .intunewin is generated natively in Python (no IntuneWinAppUtil.exe) — *shipped Phase 5*
- [x] **PKG-03**: User can export a NinjaRMM ZIP package — *shipped Phase 5*
- [x] **PKG-04**: User can upload a custom PNG icon and it is embedded in the .intunewin package — *shipped Phase 5 + wired in Phase 6 (gap closure)*
- [x] **PKG-05**: User can preview and copy Intune install/uninstall command strings before export — *shipped Phase 5*
### Infrastructure
- [x] **INFRA-01**: Application runs as a single Docker container — *shipped Phase 1*
- [x] **INFRA-02**: Application has minimal runtime dependencies (no Node.js, no external DB) — *shipped Phase 1*
## Out of Scope (v1.0 decisions — carry forward unless revisited)
| Feature | Reason |
|---------|--------|
| User authentication / logins | Internal tool on private network; simplicity over security |
| Direct Intune API push | Requires per-tenant OAuth, multi-tenant app registration — scope explosion |
| Direct NinjaRMM API push | Same as Intune — keep the tool as a package generator |
| Real-time printer status / monitoring | Requires SNMP polling and network access to client sites — different product |
| Universal Print integration | Different deployment model, requires Azure subscription |
| Mobile / tablet UI | Target users are at workstations; no validated demand |
| Multi-language / localization | English only for v1; no demand signal |
| Full audit log / deployment history | MSPs already have Intune/RMM logs |
## v2 Requirements (deferred — not touched in v1.0)
### Bulk Operations
- **BULK-01**: User can import multiple printers from CSV
- **BULK-02**: User can export all printers for a client as a batch
### Advanced Features
- **ADV-01**: Package version history per printer
- **ADV-02**: API / CLI mode for CI/CD integration
- **ADV-03**: Print server migration path (Printbrm import)
## Traceability (final)
| Requirement | Phase | Status |
|-------------|-------|--------|
| INFRA-01 | Phase 1 | Complete |
| INFRA-02 | Phase 1 | Complete |
| DRV-01 | Phase 2 | Complete |
| DRV-02 | Phase 2 | Complete |
| DRV-03 | Phase 2 | Complete |
| DRV-04 | Phase 2 | Complete |
| DRV-05 | Phase 2 | Complete |
| PRNT-01 | Phase 3 | Complete |
| PRNT-02 | Phase 3 | Complete |
| PRNT-03 | Phase 3 | Complete (runtime verification pending) |
| PRNT-04 | Phase 3 | Complete |
| PRNT-05 | Phase 3 | Complete |
| PRNT-06 | Phase 3 | Complete |
| PRNT-07 | Phase 3 | Complete |
| PRNT-08 | Phase 3 | Complete |
| PRNT-09 | Phase 3 | Complete |
| PRNT-10 | Phase 3 | Complete |
| SCRPT-01 | Phase 4 | Complete |
| SCRPT-02 | Phase 4 | Complete |
| SCRPT-03 | Phase 4 | Complete |
| SCRPT-04 | Phase 4 | Complete |
| SCRPT-05 | Phase 4 | Complete |
| PKG-01 | Phase 5 | Complete |
| PKG-02 | Phase 5 | Complete |
| PKG-03 | Phase 5 | Complete |
| PKG-04 | Phase 5 + Phase 6 | Complete (Phase 6 gap closure) |
| PKG-05 | Phase 5 | Complete |
**Coverage:**
- v1 requirements: 27 total
- Satisfied: 27
- Unmapped: 0
---
*Archived 2026-04-13 on v1.0 milestone completion. See `.planning/milestones/v1.0-ROADMAP.md` for phase details and `.planning/milestones/v1.0-MILESTONE-AUDIT.md` for audit report.*
+141
View File
@@ -0,0 +1,141 @@
# Milestone v1.0: ImpTune MVP
**Status:** ✅ SHIPPED 2026-04-13
**Phases:** 1-7
**Total Plans:** 13
**Timeline:** 2026-04-10 → 2026-04-13 (4 days)
## Overview
Initial release of ImpTune — a self-hosted single-container webapp that lets IT technicians configure printer deployments and export ready-to-deploy packages for Microsoft Intune (.intunewin) or NinjaRMM (ZIP). Ships driver ZIP upload with INF parsing, full printer configuration with client/tenant grouping, PowerShell script generation (install/uninstall/detect) with UAC elevation and WOW64 guards, Python-native .intunewin assembly with embedded icon, and NinjaRMM ZIP export — all behind a no-auth HTMX/Alpine.js browser UI.
## Phases
### Phase 1: Foundation
**Goal**: A running Docker container with the app scaffold, data schema, and validated .intunewin generation capability
**Depends on**: Nothing
**Requirements**: INFRA-01, INFRA-02
**Plans**: 3 plans
Plans:
- [x] 01-01: Docker container scaffold (python:3.12-slim-bookworm, FastAPI, Jinja2, HTMX, Alpine.js, Pico CSS, offline static baking, healthcheck, sidebar nav shell)
- [x] 01-02: SQLite schema — Peewee WAL mode, full 4-table ORM (Client/Driver/Printer/Icon) created upfront, SHA256 content-addressed DriverStore, auto-init via lifespan
- [x] 01-03: Python-native .intunewin format spike — `build_intunewin()` with AES-256-CBC, HMAC-SHA256, detection.xml, 14 byte-level validation tests
### Phase 2: Driver Management
**Goal**: Technicians upload driver packages and select driver names from parsed INF data — no free-text entry
**Depends on**: Phase 1
**Requirements**: DRV-01, DRV-02, DRV-03, DRV-04, DRV-05
**Plans**: 2 plans
Plans:
- [x] 02-01: INF parser service — TDD, RawConfigParser(strict=False), BOM/UTF-16 detection, %TOKEN% resolution, multi-model support, unused-files detection
- [x] 02-02: Driver upload endpoint + drivers page — POST /drivers/upload with ZIP validation, SHA256 dedup, Peewee persistence, HTMX partial refresh, 8 integration tests
### Phase 3: Printer Configuration
**Goal**: Technicians configure all printer parameters, assign printers to clients, and regenerate saved configs without re-uploading drivers
**Depends on**: Phase 2
**Requirements**: PRNT-01 through PRNT-10
**Plans**: 2 plans
Plans:
- [x] 03-01: Printer + Client CRUD — form with all fields, Alpine.js IP→port auto-derivation (preserves manual edits), grouped list with LEFT OUTER JOIN, HTMX outerHTML swap, integration tests covering PRNT-01..09
- [x] 03-02: Printer detail page — full-page template with all config fields, driver association, regenerate placeholder, clickable links in printer list
### Phase 4: Script Generation
**Goal**: System produces correct, production-ready PowerShell scripts handling all Intune and RMM execution contexts
**Depends on**: Phase 3
**Requirements**: SCRPT-01, SCRPT-02, SCRPT-03, SCRPT-04, SCRPT-05
**Plans**: 2 plans
Plans:
- [x] 04-01: `render_install()` with Jinja2 template — WOW64 64-bit relaunch guard, UAC self-elevation, pnputil two-step staging, duplex mapping, idempotency, plain-string args for DB-free unit testability
- [x] 04-02: `render_uninstall()` + `render_detect()` templates, 3 script download endpoints (/install, /uninstall, /detect), `_get_printer_and_driver()` shared helper, PlainTextResponse with Content-Disposition
### Phase 5: Package Export
**Goal**: Technicians download a complete, ready-to-deploy package for either Intune or NinjaRMM in one click
**Depends on**: Phase 4
**Requirements**: PKG-01, PKG-02, PKG-03, PKG-04, PKG-05
**Plans**: 2 plans
Plans:
- [x] 05-01: `/printers/{id}/packages/ninja` + `/packages/intunewin` endpoints — in-memory ZIP assembly with BytesIO, TemporaryDirectory staging for intunewin, driver ZIP existence validation
- [x] 05-02: Icon upload with Pillow validation (PNG 256x256 ≤750KB), SHA256-addressed icon storage, printer detail page with Intune Commands section (copy buttons), Export section, Icon Upload form
### Phase 6: Wire Icon into .intunewin Export (gap closure)
**Goal**: Uploaded PNG icon is embedded in the .intunewin package so Intune displays it as the app icon
**Depends on**: Phase 5
**Requirements**: PKG-04 (closes gap from first audit)
**Plans**: 1 plan
Plans:
- [x] 06-01: Wire `Icon.get_or_none()` lookup into `packages.py`, `shutil.copy2()` icon to tmpdir as `icon.png`, silent-skip on missing record/file, integration test verifying icon presence in exported package
**Details:** Added as gap-closure phase after first milestone audit flagged PKG-04 as unsatisfied — icon was uploaded and stored but never embedded in the .intunewin output.
### Phase 7: Dashboard & Navigation Polish (gap closure)
**Goal**: Navigation links work correctly and dashboard shows real data instead of empty placeholders
**Depends on**: Phase 3
**Requirements**: None (UX/integration fixes)
**Plans**: 1 plan
Plans:
- [x] 07-01: Add `GET /packages` route (LEFT OUTER join on Client + Driver, `switch(Printer)`), wire dashboard `recent_printers` / `recent_packages` to live DB queries, new `packages.html` template, clickable nav links
**Details:** Added as gap-closure phase after first milestone audit flagged `base.html → /packages` 404 (route missing) and hardcoded `[]` in dashboard queries.
---
## Milestone Summary
**Key Decisions:**
- **Python-native .intunewin** — IntuneWinAppUtil.exe is a Windows PE binary, cannot run in Linux container. Reimplemented the AES-256-CBC / HMAC-SHA256 format in Python with pycryptodome, validated byte-level against the C# reference. ✓ Good
- **Stack:** Python 3.12 + FastAPI + Jinja2 + HTMX + Alpine.js + SQLite + Peewee + pycryptodome + Pillow. ✓ Good — minimal runtime, single container, no Node.js
- **Full 4-table schema upfront (Phase 1)** — Client/Driver/Printer/Icon all created in 01-02 so later phases add routes only, no schema migrations. ✓ Good
- **Sync FastAPI routes** — runs in thread pool, Peewee-compatible without async ORM complexity. ✓ Good
- **Content-addressed storage** — SHA256 for drivers and icons, deduplication for free, consistent pattern. ✓ Good
- **Plain-string args for script generators** (not ORM objects) — keeps unit tests DB-free. ✓ Good
- **Test isolation** — TestClient used as context manager for Starlette 0.46+ lifespan; thread-local Peewee connections closed in conftest teardown; `list(Model.select())` wrapper avoids cursor caching across DB re-inits. ✓ Good
- **Silent-skip on missing icon** — export always succeeds regardless of icon presence, optional feature. ✓ Good
- **Gap-closure phases 6 & 7** — added post-audit rather than shipping with known defects; cleaner than carrying PKG-04 and /packages 404 as tech debt into v1.1. ✓ Good
**Issues Resolved:**
- Peewee `datetime.utcnow()` deprecation warning (root cause was project-level `_utcnow()` usage, not library)
- DriverStore path mismatch bug — `.zip` suffix inconsistency between save and lookup broke upload→export flow in production (masked by pre-staged test fixtures); centralized in `DriverStore.get_path()` and added `tests/test_upload_export_roundtrip.py` regression test
- Uninstall copy button mislabel ("Uninstall copy" → "Copy")
- Stale `requirements-completed` frontmatter in 5 SUMMARY.md files (back-filled)
- PKG-04 icon→.intunewin wiring break (Phase 6)
- `/packages` 404 + dashboard hardcoded `[]` (Phase 7)
**Issues Deferred to v1.1 (Tech Debt):**
- Printer form driver dropdown requires manual page reload after uploading a new driver on /drivers (Phase 2)
- PRNT-03 Alpine.js port auto-derivation — code correct, needs live browser verification (Phase 3)
- No UI links to individual script downloads — only accessible via package export or direct URL (Phase 5)
**Nyquist Validation:** All 7 phases have draft VALIDATION.md files but none are Nyquist-compliant. Wave 0 not complete. Not a milestone blocker — separate validation track for v1.1.
**Known Runtime Validations Pending:**
- `.intunewin` byte-level format must be validated against a real Intune tenant
- `pnputil` + `$PSScriptRoot` path resolution under SYSTEM context on a real Intune-managed device
---
*For current project status, see `.planning/ROADMAP.md`*
@@ -0,0 +1,124 @@
---
milestone: v1.0
type: validation-index
audit_date: 2026-04-13
auditor: Sébastien QUEROL
status: signed-off
signed_off_by: Sébastien QUEROL
signed_off_date: 2026-04-13
---
# v1.0 Validation Index — Nyquist Rollup
**Audit date:** 2026-04-13
**Auditor:** Sébastien QUEROL (signed off 2026-04-13)
**Compiled by:** Claude (gsd-executor, plan 08-08)
**Tally:** 45/45 pass, 0 deferred-v1.2, 0 fail-fix-v1.1, 0 wont-do
Single flat pass/fail roll-up of every v1.0 success criterion across Phases 17. Source of truth for NYQ-02 and NYQ-03. Per-phase Nyquist Records are embedded in each `NN-VALIDATION.md` and enumerated one row per criterion below. Row counts per phase (14, 6, 10, 5, 5, 1, 4) are a function of scope — single-criterion gap-closure phases legitimately produce single-row audits.
**Key roll-up facts (for downstream verifier):**
- **Phase 5 row 2 (PKG-02) is the only artifact-backed live-tenant runtime row** in the entire track — cites RTVAL-01 PASS on tenant rubis.fr (2026-04-13) after structural fixes in commits `74535ea` + `7716246`.
- **Phase 4 rows 15 (SCRPT-01..05) rest on attestation-only runtime proof** (RTVAL-02/03/04, three consecutive attestation-only PASSes per STATE.md 2026-04-13). User was warned twice about cumulative audit-trail damage and explicitly approved proceeding. Phase 10 plan 10-03 signed off with written acknowledgement. Re-capture with full artifacts owned by Phase 11 rollout.
- **Phase 1 row 14, Phase 2 row 6, Phase 3 row 3, Phase 5 row 4, Phase 7 row 4** are historical-gap closure rows — all resolved in place via fixing phases (Phase 9 UX-01/02/03, Phase 6 icon embedding, Phase 10 RTVAL-01) with direct commit citations, not flipped to fail-fix-v1.1.
- **Phase 7 is the only REQUIREMENTS-free phase**, anchored to `07-VERIFICATION.md` truths rather than `REQUIREMENTS.md` IDs. Legitimate alternate anchoring pattern.
- **Bidirectional citation loop:** 05-VALIDATION row 4 ↔ 06-VALIDATION row 1 both cite `TestIntunewinIconInclusion` — closed-loop gap-closure pattern worth replicating for future gap-closure phases.
## Flat Pass/Fail Table
| Phase | # | Criterion | Status | Evidence | Gap Link |
|---|---|---|---|---|---|
| 1-Foundation | 1 | `docker compose up` serves HTTP 200 on `GET /health` | pass | [01-VALIDATION.md#nyquist-record](../phases/01-foundation/01-VALIDATION.md#nyquist-record) row 1 (test_health + 01-VERIFICATION row 1) | |
| 1-Foundation | 2 | No Node.js dependency; single `python:3.12-slim-bookworm` base image | pass | [01-VALIDATION.md row 2](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (Dockerfile line 1, commit 34c7cb3) | |
| 1-Foundation | 3 | All static assets served from `/static/` with zero CDN refs | pass | [01-VALIDATION.md row 3](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (test_no_cdn_urls_in_templates) | |
| 1-Foundation | 4 | Sidebar shows Dashboard / Drivers / Printers / Clients / Packages | pass | [01-VALIDATION.md row 4](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (base.html nav + Phase 7 /packages closure) | |
| 1-Foundation | 5 | App follows OS dark/light theme | pass | [01-VALIDATION.md row 5](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (base.html `data-theme="auto"`) | |
| 1-Foundation | 6 | SQLite initializes with all 4 tables on first run | pass | [01-VALIDATION.md row 6](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (test_create_tables, commit 88d9c5f) | |
| 1-Foundation | 7 | DB uses WAL journal + foreign keys enabled | pass | [01-VALIDATION.md row 7](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (test_wal_mode + test_foreign_keys) | |
| 1-Foundation | 8 | DB file lives in `DATA_DIR` volume, not container FS | pass | [01-VALIDATION.md row 8](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (docker-compose volume + cfg.DB_PATH) | |
| 1-Foundation | 9 | Schema creation is idempotent across restarts | pass | [01-VALIDATION.md row 9](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (test_idempotent, `safe=True`) | |
| 1-Foundation | 10 | Python function produces valid `.intunewin` from source dir + setup file | pass | [01-VALIDATION.md row 10](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (test_output_is_valid_zip, commit 25f82e6) | |
| 1-Foundation | 11 | `.intunewin` outer ZIP has correct `IntuneWinPackage/` structure | pass | [01-VALIDATION.md row 11](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (test_outer_zip_structure) | |
| 1-Foundation | 12 | Encrypted blob byte layout: HMAC(32) + IV(16) + AES-256-CBC ciphertext | pass | [01-VALIDATION.md row 12](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (3 byte-layout tests) | |
| 1-Foundation | 13 | Detection.xml cryptographic fields match actual encryption | pass | [01-VALIDATION.md row 13](../phases/01-foundation/01-VALIDATION.md#nyquist-record) (5 crypto-field tests) | |
| 1-Foundation | 14 | `.intunewin` accepted by real Intune tenant end-to-end | pass | [01-VALIDATION.md row 14](../phases/01-foundation/01-VALIDATION.md#nyquist-record) → Phase 10 `RUNTIME-VALIDATION.md` RTVAL-01 PASS 2026-04-13 on rubis.fr (commits 74535ea + 7716246) | |
| 2-Drivers | 1 | **DRV-01** User uploads driver package (ZIP+INF) via web UI | pass | [02-VALIDATION.md row 1](../phases/02-driver-management/02-VALIDATION.md#nyquist-record) (test_upload_valid_zip/non_zip/no_inf, commit c648fc5) | |
| 2-Drivers | 2 | **DRV-02** INF parser extracts DriverDesc with encoding + token handling | pass | [02-VALIDATION.md row 2](../phases/02-driver-management/02-VALIDATION.md#nyquist-record) (16 tests in test_inf_parser.py) | |
| 2-Drivers | 3 | **DRV-03** User selects driver name from parsed-INF dropdown (no free-text) | pass | [02-VALIDATION.md row 3](../phases/02-driver-management/02-VALIDATION.md#nyquist-record) (test_drivers_page + test_upload_returns_select) | |
| 2-Drivers | 4 | **DRV-04** Uploaded driver ZIP persisted content-addressed; dedupes on re-upload | pass | [02-VALIDATION.md row 4](../phases/02-driver-management/02-VALIDATION.md#nyquist-record) (test_driver_persisted + test_dedup_upload) | |
| 2-Drivers | 5 | **DRV-05** System flags unused files not referenced by INF | pass | [02-VALIDATION.md row 5](../phases/02-driver-management/02-VALIDATION.md#nyquist-record) (test_unused_files + test_unused_files_in_response) | |
| 2-Drivers | 6 | **DRV-01 runtime gap:** `POST /drivers/upload` must not return 500 on real ZIPs | pass | [02-VALIDATION.md row 6](../phases/02-driver-management/02-VALIDATION.md#nyquist-record) → Phase 9 UX-01 commits d1de839 + 10ee09a + 72c6a98 | Historical gap closed in place via Phase 9 UX-01 (REQUIREMENTS.md UX-01 Complete). Resolved 2026-04-13. |
| 3-Printer | 1 | **PRNT-01** User sets printer display name | pass | [03-VALIDATION.md row 1](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_create_printer_persisted, commit 356c2ee) | |
| 3-Printer | 2 | **PRNT-02** User sets printer IP address or hostname | pass | [03-VALIDATION.md row 2](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_create_printer_persisted ip_address field) | |
| 3-Printer | 3 | **PRNT-03** System auto-suggests port name from IP; manual edits preserved | pass | [03-VALIDATION.md row 3](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) → Phase 9 UX-02 Playwright commits 322fc20 + 37a06da | Historical gap closed in place via Phase 9 UX-02 (Playwright headless chromium e2e). Sole `NEEDS HUMAN` truth from 03-VERIFICATION.md 2026-04-10. |
| 3-Printer | 4 | **PRNT-04** User sets duplex mode (OneSided/LongEdge/ShortEdge) | pass | [03-VALIDATION.md row 4](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_create_printer_duplex) | |
| 3-Printer | 5 | **PRNT-05** User sets color vs. grayscale default | pass | [03-VALIDATION.md row 5](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_create_printer_color_mode) | |
| 3-Printer | 6 | **PRNT-06** User sets paper size (A4/Letter/Legal) | pass | [03-VALIDATION.md row 6](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_create_printer_paper_size) | |
| 3-Printer | 7 | **PRNT-07** User sets collate on/off | pass | [03-VALIDATION.md row 7](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_create_printer_collate) | |
| 3-Printer | 8 | **PRNT-08** User assigns printer to a client/tenant label | pass | [03-VALIDATION.md row 8](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_printer_grouped_by_client, LEFT OUTER join) | |
| 3-Printer | 9 | **PRNT-09** Printer configurations persist across SQLite sessions | pass | [03-VALIDATION.md row 9](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_printer_survives_page_refresh) | |
| 3-Printer | 10 | **PRNT-10** Detail page loads full config with driver FK intact (Phase-3 scope) | pass | [03-VALIDATION.md row 10](../phases/03-printer-configuration/03-VALIDATION.md#nyquist-record) (test_printer_detail_shows_driver + no_driver) | |
| 4-Scripts | 1 | **SCRPT-01** Install script: pnputil + Add-Printer* + Set-PrintConfiguration | pass | [04-VALIDATION.md row 1](../phases/04-script-generation/04-VALIDATION.md#nyquist-record) (test_render_install_* + RTVAL-02 attestation-only) | Runtime half is attestation-only per STATE.md 2026-04-13 — no IntuneManagementExtension.log excerpt or portal screenshot. Phase 11 rollout owns artifact re-capture. Template correctness fully pytest-automated. |
| 4-Scripts | 2 | **SCRPT-02** Uninstall script: Remove-Printer → Remove-PrinterDriver → Remove-PrinterPort | pass | [04-VALIDATION.md row 2](../phases/04-script-generation/04-VALIDATION.md#nyquist-record) (test_render_uninstall + RTVAL-04 attestation-only) | Third consecutive attestation-only check; no rtval-04-uninstall-log.txt or rtval-04-uninstall-status.png captured. Phase 10 plan 10-03 signed off with written acknowledgement. Phase 11 owns re-capture. |
| 4-Scripts | 3 | **SCRPT-03** Detect script: exit 0 when present / exit 1 when absent | pass | [04-VALIDATION.md row 3](../phases/04-script-generation/04-VALIDATION.md#nyquist-record) (test_render_detect + RTVAL-03 attestation-only) | Second consecutive attestation-only check; no rtval-03-detection.png captured. REQUIREMENTS.md wording ("registry check") superseded by 04-RESEARCH.md decision to use `Get-Printer` cmdlet. Phase 11 owns re-capture. |
| 4-Scripts | 4 | **SCRPT-04** Install script detects SYSTEM vs user and self-elevates via UAC | pass | [04-VALIDATION.md row 4](../phases/04-script-generation/04-VALIDATION.md#nyquist-record) (test_render_install_uac_guard + RTVAL-02 SYSTEM branch) | SYSTEM branch exercised attestation-only in RTVAL-02; user-interactive UAC dialog branch was NOT exercised in Phase 10 at all — remains a Manual-Only Verification. |
| 4-Scripts | 5 | **SCRPT-05** Install script includes 64-bit WOW64 SysNative relaunch guard | pass | [04-VALIDATION.md row 5](../phases/04-script-generation/04-VALIDATION.md#nyquist-record) (test_render_install_wow64_guard + RTVAL-02 attestation-only) | WOW64 relaunch path not directly observable from RTVAL-02 attestation; template-level positional correctness (guard before pnputil) is fully pytest-automated. Full WOW64 trace is a Phase 11 rollout concern. |
| 5-Package | 1 | **PKG-01** User exports full `.intunewin` package in one click | pass | [05-VALIDATION.md row 1](../phases/05-package-export/05-VALIDATION.md#nyquist-record) (TestIntunewinDownload 4 tests + RTVAL-01 artifact-backed PASS on rubis.fr) | |
| 5-Package | 2 | **PKG-02** `.intunewin` generated natively in Python (no IntuneWinAppUtil.exe); byte-level conformant | pass | [05-VALIDATION.md row 2](../phases/05-package-export/05-VALIDATION.md#nyquist-record) (14 byte-level tests in test_intunewin.py + **artifact-backed** RTVAL-01 PASS after fix commits 74535ea + 7716246) | **Strongest row in the entire 7-phase track** — only artifact-backed live-tenant runtime evidence. Initial RTVAL-01 FAILED; root cause was two structural defects fixed in commits `74535ea` (HMAC over IV+ciphertext) + `7716246` (Detection.xml alignment with IntuneWinAppUtil.exe reference format); re-test PASSED on tenant rubis.fr with committed screenshots + package. |
| 5-Package | 3 | **PKG-03** User exports NinjaRMM ZIP package in one click | pass | [05-VALIDATION.md row 3](../phases/05-package-export/05-VALIDATION.md#nyquist-record) (TestNinjaDownload 5 tests) | |
| 5-Package | 4 | **PKG-04** User uploads custom PNG icon; embedded into `.intunewin` | pass | [05-VALIDATION.md row 4](../phases/05-package-export/05-VALIDATION.md#nyquist-record) → Phase 6 `TestIntunewinIconInclusion` (commits 2723cc8 + 6310be5) | Historical gap closed in place via Phase 6 (Wire Icon into .intunewin Export). Upload half shipped in Phase 5 plan 02; embedding half added in Phase 6. Bidirectional citation loop with 06-VALIDATION row 1. |
| 5-Package | 5 | **PKG-05** User previews and copies Intune install/uninstall command strings before export | pass | [05-VALIDATION.md row 5](../phases/05-package-export/05-VALIDATION.md#nyquist-record) (TestCommandPreview 4 tests) | |
| 6-Icon-Wire | 1 | **PKG-04 embedding:** Uploaded PNG icon embedded in `.intunewin` output | pass | [06-VALIDATION.md row 1](../phases/06-wire-icon-intunewin/06-VALIDATION.md#nyquist-record) (TestIntunewinIconInclusion 2 tests, `shutil.copy2` staging at packages.py:153 before build_intunewin at :157; commits 2723cc8 + 6310be5; RTVAL-01 transitive) | Bidirectional closure loop with 05-VALIDATION row 4. Icon-tile visual rendering on Intune portal is Manual-Only polish owned by Phase 11 rollout. |
| 7-Dashboard | 1 | `GET /packages` returns 200 and lists driver-assigned printers (closes milestone-audit /packages 404) | pass | [07-VALIDATION.md row 1](../phases/07-dashboard-nav-polish/07-VALIDATION.md#nyquist-record) (test_packages_returns_200, pages.py:142-158, commits 8cf47f5 + 91910ad) | |
| 7-Dashboard | 2 | Dashboard shows 5 most recent printers via live query | pass | [07-VALIDATION.md row 2](../phases/07-dashboard-nav-polish/07-VALIDATION.md#nyquist-record) (test_dashboard_shows_recent_printers, pages.py:20-22) | |
| 7-Dashboard | 3 | Dashboard shows 5 most recent packages (driver-filtered) via live query | pass | [07-VALIDATION.md row 3](../phases/07-dashboard-nav-polish/07-VALIDATION.md#nyquist-record) (test_dashboard_shows_recent_packages, pages.py:23-28 with `Printer.driver.is_null(False)` filter) | |
| 7-Dashboard | 4 | **UX-03 carry-over (Phase 5 origin):** Individual script download links on printer detail page | pass | [07-VALIDATION.md row 4](../phases/07-dashboard-nav-polish/07-VALIDATION.md#nyquist-record) → Phase 9 plan 09-03 commits d359001 + 68a2935 | Historical gap closed in place via Phase 9 UX-03 (.ps1 route aliases + printer_detail Scripts section). Provenance note: v1.0-ROADMAP.md lists UX-03 as Phase 5 deferral, not Phase 7 — STATE.md restatement imprecise; resolution unaffected. |
## Gap Validation Block
All five historical-gap rows cite fixing phases/commits. Each citation has been cross-checked against `REQUIREMENTS.md` and the fixing phase's SUMMARY.md:
| # | Row | Recorded as | Citation target | Fix owner | Confirmed? |
|---|-----|-------------|-----------------|-----------|------------|
| 1 | Phase 1 row 14 (real Intune tenant ingestion) | pass | Phase 10 RTVAL-01 commits 74535ea + 7716246 | Phase 10 plan 10-02 | ✓ (REQUIREMENTS.md RTVAL-01 Complete; plan 10-03 sign-off commit cd2df1e) |
| 2 | Phase 2 row 6 (POST /drivers/upload 500) | pass | Phase 9 UX-01 commits d1de839 + 10ee09a + 72c6a98 | Phase 9 plan 09-01 | ✓ (REQUIREMENTS.md UX-01 Complete; 09-01-SUMMARY.md) |
| 3 | Phase 3 row 3 (PRNT-03 Alpine.js IP→port live-browser) | pass | Phase 9 UX-02 commits 322fc20 + 37a06da | Phase 9 plan 09-02 | ✓ (REQUIREMENTS.md UX-02 Complete; 09-02-SUMMARY.md) |
| 4 | Phase 5 row 4 / Phase 6 row 1 (PKG-04 icon embedding) | pass | Phase 6 TestIntunewinIconInclusion commits 2723cc8 + 6310be5 | Phase 6 plan 06-01 | ✓ (bidirectional closure loop confirmed between 05-VALIDATION row 4 and 06-VALIDATION row 1) |
| 5 | Phase 7 row 4 (UX-03 individual script downloads) | pass | Phase 9 plan 09-03 commits d359001 + 68a2935 | Phase 9 plan 09-03 | ✓ (REQUIREMENTS.md UX-03 Complete; 09-03-SUMMARY.md; provenance note flagged — Phase 5 origin per v1.0-ROADMAP.md) |
**Result:** All 5 fail-fix-equivalent rows point to real fixing phases with shipped commits. **No roadmap-mismatch detected.** Zero rows inflated to `fail-fix-v1.1` because every historical gap is already closed in the tree.
### Attestation-Gap Residual Risk (recorded for rollout)
Phase 4 rows 15 (SCRPT-01..05) carry attestation-only runtime proof via RTVAL-02/03/04 per STATE.md 2026-04-13. This is **NOT** a fail-fix row (Phase 10 plan 10-03 explicitly signed off the gap with written acknowledgement; the user was warned twice and approved). It is documented here as a known weakness in the v1.0 runtime audit trail and is owned by **Phase 11 Real-World Rollout** for artifact re-capture (IntuneManagementExtension.log excerpt, portal screenshots, status captures) before broad deployment. Not in the above gap table because there is no "fix commit" — the fix is to re-run with full evidence capture, which is a rollout-phase action, not a code change.
## Tally Summary
| Source | Row count |
|---|---:|
| Phase 1 — Foundation | 14 |
| Phase 2 — Driver Management | 6 |
| Phase 3 — Printer Configuration | 10 |
| Phase 4 — Script Generation | 5 |
| Phase 5 — Package Export | 5 |
| Phase 6 — Wire Icon into .intunewin | 1 |
| Phase 7 — Dashboard & Nav Polish | 4 |
| **Total** | **45** |
| Status | Count |
|---|---:|
| pass | 45 |
| fail-fix-v1.1 | 0 |
| deferred-v1.2 | 0 |
| wont-do | 0 |
**NYQ-01 coverage:** 7/7 v1.0 phases have Nyquist-compliant `VALIDATION.md` files with one observable check per success criterion, evidence cited, no hand-wavy "code looks right" entries. Ticked in REQUIREMENTS.md after 08-01 / 08-05 / 08-06 / 08-07 executions.
**NYQ-02 coverage:** This document is the single flat pass/fail rollup. Will be ticked in REQUIREMENTS.md upon sign-off.
**NYQ-03 coverage:** Every non-pass row has a rationale. No rows are non-pass — all 45 rows are `pass`, all historical gaps closed in place with fixing-phase citations, all cross-checked against REQUIREMENTS.md and fixing-phase SUMMARY.md files. Will be ticked in REQUIREMENTS.md upon sign-off.
## Sign-Off
- [x] Nyquist audit complete — 2026-04-13 — Sébastien QUEROL