- Flip v1.0-VALIDATION-INDEX.md frontmatter draft -> signed-off (Sébastien QUEROL, 2026-04-13) - Tick index sign-off line (45/45 pass, 0 fail-fix-v1.1) - Replicate dated sign-off line into all 7 per-phase VALIDATION.md Validation Sign-Off blocks - Tick nyquist_compliant checkbox in phases 01/02/03/04/05/07 (06 already ticked) - Upgrade Approval lines phases 01-05 from 'pending' to signed-off with index back-link - Tick REQUIREMENTS.md NYQ-02/NYQ-03 and flip Traceability Pending -> Complete - Phase 4 attestation-only runtime gap recorded in index Residual Risk block (not reopened per plan 10-03 sign-off)
15 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created, nyquist_audited, nyquist_auditor
| phase | slug | status | nyquist_compliant | wave_0_complete | created | nyquist_audited | nyquist_auditor |
|---|---|---|---|---|---|---|---|
| 5 | package-export | draft | true | false | 2026-04-10 | 2026-04-13 | Claude (gsd-executor, plan 08-05) |
Phase 5 — Validation Strategy
Per-phase validation contract for feedback sampling during execution.
Test Infrastructure
| Property | Value |
|---|---|
| Framework | pytest (existing test suite) |
| Config file | none — runs with pytest tests/ |
| Quick run command | pytest tests/test_packages.py tests/test_icon_upload.py -x |
| Full suite command | pytest tests/ -x |
| Estimated runtime | ~10 seconds |
Sampling Rate
- After every task commit: Run
pytest tests/test_packages.py tests/test_icon_upload.py -x - After every plan wave: Run
pytest tests/ -x - Before
/gsd:verify-work: Full suite must be green - Max feedback latency: 10 seconds
Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|
| 05-01-01 | 01 | 1 | PKG-03 | integration | pytest tests/test_packages.py::TestNinjaDownload -x |
❌ W0 | ⬜ pending |
| 05-02-01 | 02 | 1 | PKG-01 | integration | pytest tests/test_packages.py::TestIntunewinDownload -x |
❌ W0 | ⬜ pending |
| 05-02-02 | 02 | 1 | PKG-02 | unit | pytest tests/test_intunewin.py -x |
✅ | ⬜ pending |
| 05-03-01 | 03 | 2 | PKG-04 | integration | pytest tests/test_icon_upload.py -x |
❌ W0 | ⬜ pending |
| 05-03-02 | 03 | 2 | PKG-05 | integration | pytest tests/test_packages.py::TestCommandPreview -x |
❌ W0 | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Wave 0 Requirements
tests/test_packages.py— stubs for PKG-01, PKG-03, PKG-05tests/test_icon_upload.py— stubs for PKG-04requirements.txt— addPillow>=10.0if icon dimension validation is implemented
PKG-02: existing tests/test_intunewin.py already covers the format — no new test file needed.
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
| .intunewin accepted by real Intune tenant | PKG-01 | Requires live Azure/Intune environment | Upload generated .intunewin via Intune portal → verify app appears without errors |
Nyquist Record
Audited 2026-04-13 by Claude (gsd-executor, plan 08-05). One row per Phase 5 success criterion derived from
milestones/v1.0-ROADMAP.mdPhase 5 goal + PKG-01..05 (REQUIREMENTS.mdv1.0 block), cross-checked against05-VERIFICATION.md(11/11 observable truths VERIFIED 2026-04-10) and the Phase 5 plan summaries (05-01-SUMMARY.md,05-02-SUMMARY.md). Evidence cites committed pytest invocations, source lines, commit SHAs, the dated VERIFICATION report, and — for byte-level .intunewin conformance — Phase 10RUNTIME-VALIDATION.mdRTVAL-01 (tenant ingestion) which is the only artifact-backed runtime row in Phase 10 per STATE.md 2026-04-13.Phase 5 goal (v1.0-ROADMAP.md): "Technicians download a complete, ready-to-deploy package for either Intune or NinjaRMM in one click."
Byte-level .intunewin conformance (key point for this audit): Phase 5 shipped with the .intunewin format as a MEDIUM confidence concern —
test_intunewin.pyvalidates 14 byte-level truths (outer ZIP, Detection.xml fields, AES-256-CBC/HMAC-SHA256 crypto, IV/key lengths, file digest, unencrypted size) but could not prove real-Intune acceptance. Phase 10 RTVAL-01 closed that gap: initial 2026-04-13 upload to tenant rubis.fr failed with greyed-out wizard (ISSUE-01), root-caused to two structural defects — (1) HMAC over ciphertext only instead of IV+ciphertext, (2) Detection.xml not matching IntuneWinAppUtil.exe reference — both fixed in commits74535ea(HMAC over IV+ciphertext) and7716246(Detection.xml alignment). Re-test on the fixed build PASSED against live tenant rubis.fr on 2026-04-13 (wizard parsed cleanly, all fields populated, assignment saved). Plan 10-03 signed off the result (commitcd2df1e). This makes PKG-02 the only Phase 5 row with artifact-backed real-tenant runtime evidence.PKG-04 icon embedding: Phase 5 plan 02 shipped icon upload + storage but did NOT embed the icon into the .intunewin output. This was caught by the v1.0 first milestone audit, which spawned gap-closure Phase 6 (Wire Icon into .intunewin Export). The PKG-04 row below therefore cites the Phase 6 closure test (
tests/test_packages.py::TestIntunewinIconInclusion::test_intunewin_includes_icon) as the definitive evidence, with Phase 5 row noted as "historically incomplete, closed by Phase 6". This mirrors the 08-02 row-6 historical-gap-closure pattern.
| # | Success Criterion | Observable Check | Evidence | Status | Notes |
|---|---|---|---|---|---|
| 1 | PKG-01 — User can export a complete .intunewin package (install.ps1 + uninstall.ps1 + detect.ps1 + extracted drivers + metadata) in one click |
pytest tests/test_packages.py::TestIntunewinDownload::test_returns_intunewin + ::test_intunewin_is_valid_zip + ::test_404_missing_printer + ::test_422_no_driver — integration tests assert GET /printers/{id}/packages/intunewin returns 200 application/octet-stream, the outer container is a valid ZIP with IntuneWinPackage/ structure, and error paths return correct HTTP codes |
tests/test_packages.py class TestIntunewinDownload (4 tests, all PASS per 05-VERIFICATION.md truth 2); imptune/api/packages.py get_intunewin_package() lines 97-158 (writes install.ps1/uninstall.ps1/detect.ps1 into TemporaryDirectory, extracts driver ZIP into tmpdir/drivers/, calls build_intunewin(tmpdir, "install.ps1", output_path)); commits a31c71e (RED), dd6cedf (GREEN); 05-VERIFICATION.md truth 2; Phase 10 RUNTIME-VALIDATION.md RTVAL-01 PASS (artifact-backed re-test 2026-04-13 on tenant rubis.fr, package Copieur_2eme.intunewin SHA256 8818124a..., screenshots rtval-01-tenant-upload.png + rtval-01-app-assigned.png) |
pass | Artifact-backed runtime proof via RTVAL-01 — unique among Phase 5 rows. Intune Win32 wizard parsed the generated .intunewin, populated all fields (name, platform, size, MAM enabled), and saved the assignment to the test device group on live tenant rubis.fr. End-to-end install/uninstall/detect under SYSTEM is owned by Phase 4 rows (attestation-only there); this row covers only "Intune accepts the package", which RTVAL-01 proves artifact-backed. |
| 2 | PKG-02 — .intunewin is generated natively in Python (no IntuneWinAppUtil.exe subprocess dependency) and is byte-level conformant with the Microsoft format specification |
pytest tests/test_intunewin.py — 14 byte-level assertions across 5 test classes: TestOuterZipStructure (valid ZIP, IntuneWinPackage/ present, stored compression), TestDetectionXml (XML valid, required fields present, setup file named), TestEncryptedBlobLayout (blob layout, IV=16 bytes, encryption key=32 bytes, MAC key=32 bytes), TestCryptographicVerification (HMAC matches over IV+ciphertext, AES-256-CBC decryption roundtrip, file digest matches), and unencrypted content size check |
tests/test_intunewin.py (14 tests, all PASS — existing test file per 05-VERIFICATION.md Wave 0 note "tests/test_intunewin.py already covers the format"); imptune/generators/intunewin_builder.py (build_intunewin(), AES-256-CBC + HMAC-SHA256 + Detection.xml generator); imptune/api/packages.py line 149 build_intunewin(tmpdir, "install.ps1", output_path) — no subprocess import anywhere in phase files per 05-VERIFICATION.md Anti-Patterns section; commits 74535ea (HMAC over IV+ciphertext fix) + 7716246 (Detection.xml aligned with IntuneWinAppUtil.exe reference format) — the two structural fixes that flipped RTVAL-01 from FAIL to PASS; 05-VERIFICATION.md truth 5; Phase 10 RUNTIME-VALIDATION.md RTVAL-01 artifact-backed PASS on tenant rubis.fr (2026-04-13, evidence rtval-01-tenant-upload.png, rtval-01-app-assigned.png, Copieur_2eme.intunewin committed to evidence/) |
pass | This is the only Phase 5 row with artifact-backed live-tenant runtime proof. Closes the MEDIUM-confidence gap that 05-VERIFICATION.md flagged as "Human Verification Required #1: .intunewin byte-level Intune compatibility". Initial RTVAL-01 on 2026-04-13 FAILED (ISSUE-01: greyed-out wizard) — root cause was the two structural defects fixed in commits 74535ea + 7716246. Re-test on fixed build PASSED: Intune parsed the .intunewin cleanly, all wizard fields populated, OK button enabled, assignment saved. Plan 10-03 signed off (commit cd2df1e). No subprocess calls in any phase file — Python-native builder is the only code path. |
| 3 | PKG-03 — User can export a NinjaRMM ZIP package (rendered install.ps1 + extracted driver folder) in one click |
pytest tests/test_packages.py::TestNinjaDownload::test_returns_zip + ::test_zip_contains_install_script + ::test_zip_contains_driver_files + ::test_404_missing_printer + ::test_422_no_driver — integration tests assert GET /printers/{id}/packages/ninja returns 200 application/zip, the ZIP contains {safe_name}/install.ps1 (rendered, with pnputil), contains {safe_name}/drivers/* (extracted from driver store), and error paths return 404/422 |
tests/test_packages.py class TestNinjaDownload (5 tests, all PASS per 05-VERIFICATION.md truths 1 + 4); imptune/api/packages.py get_ninja_package() lines 49-94 (in-memory io.BytesIO + zipfile.ZipFile(ZIP_DEFLATED), {safe_name}/install.ps1 path line 82, driver file extraction loop); commits a31c71e (RED), dd6cedf (GREEN); 05-VERIFICATION.md truths 1 + 4; no runtime proof needed — NinjaRMM package is a plain ZIP downloaded by the technician and fed into their own RMM, no format-spec byte layout to defend |
pass | Template-level + HTTP-level correctness fully automated via pytest. No Phase 10 runtime row needed: NinjaRMM packages are opaque ZIPs to Intune and the target RMM handles execution context. Phase 11 rollout will exercise real NinjaRMM deployment on operator feedback; not a v1.0 milestone concern. |
| 4 | PKG-04 — User can upload a custom PNG icon for Intune app display (256x256, max 750KB, PNG format), which is stored and embedded into the .intunewin output so Intune displays it as the app icon |
pytest tests/test_icon_upload.py (6 tests: valid PNG accepted, non-PNG rejected, >750KB rejected, wrong dimensions rejected, upload replaces existing, 404 on missing printer) AND pytest tests/test_packages.py::TestIntunewinIconInclusion::test_intunewin_includes_icon + ::test_intunewin_without_icon_succeeds — asserts icon upload validation works AND that a subsequent .intunewin export embeds the icon bytes (with silent-skip fallback when no icon uploaded) |
Icon upload: tests/test_icon_upload.py (6 tests PASS per 05-VERIFICATION.md truths 6+7+8); imptune/api/icons.py lines 41-74 (Pillow-based PNG validation, SHA256-addressed storage under DATA_DIR/icons/); 05-02-SUMMARY.md commits d8ce223 (RED) + f9e13ba (GREEN). Icon→.intunewin embedding (PKG-04 gap closure): tests/test_packages.py::TestIntunewinIconInclusion (2 tests, class at line 209, shipped by Phase 6 per v1.0-ROADMAP.md Phase 6 "Wire Icon into .intunewin Export"); 05-VERIFICATION.md truths 6+7+8 for upload half; v1.0-ROADMAP.md "Issues Resolved" entry: "PKG-04 icon→.intunewin wiring break (Phase 6)"; Phase 10 RUNTIME-VALIDATION.md RTVAL-01 PASS (the package that Intune accepted was Copieur_2eme.intunewin which passed through the same builder path as icon-embedded packages) |
pass | Historical gap closed by Phase 6. Phase 5 plan 02 shipped icon upload + storage but did NOT embed the icon into the .intunewin output — caught by the v1.0 first milestone audit. Phase 6 (Wire Icon into .intunewin Export) added TestIntunewinIconInclusion with silent-skip fallback and PKG-04 was re-ticked. This row records the closure in place rather than flipping to fail-fix-v1.1, consistent with the 08-02 row-6 (drivers/upload 500 historical gap → Phase 9 UX-01 closure) pattern. Real-tenant "icon renders in Intune catalog tile" visual verification is a Phase 11 rollout concern (RWR-0x). |
| 5 | PKG-05 — User can preview and copy Intune install/uninstall command strings from the printer detail page before export | pytest tests/test_packages.py::TestCommandPreview::test_detail_page_shows_commands + ::test_detail_page_shows_export_links + ::test_detail_page_hides_commands_without_driver + ::test_detail_page_shows_icon_upload_form — asserts the rendered printer detail page contains id="install-cmd" + id="uninstall-cmd" elements with the correct command strings, hides the section when no driver is assigned, and shows both NinjaRMM ZIP + .intunewin export links |
tests/test_packages.py class TestCommandPreview (4 tests PASS per 05-VERIFICATION.md truths 9+10+11); imptune/api/pages.py lines 102-103 (passes install_cmd + uninstall_cmd into template context); imptune/templates/printer_detail.html lines 31+40 (id="install-cmd", id="uninstall-cmd"), lines 32-36 + 41-45 (Alpine.js copy-to-clipboard buttons with copiedInstall / copiedUninstall state), lines 49-50 (download hrefs packages/ninja + packages/intunewin); 05-02-SUMMARY.md commit f96ea6f; 05-VERIFICATION.md truths 9+10+11 |
pass | Alpine.js copy-to-clipboard UX (clipboard API interaction, "Copied!" state, 2-second revert) is a Manual-Only Verification (flagged as "Human Verification Required #2" in 05-VERIFICATION.md) and was NOT exercised in Phase 10 — Phase 10 focused exclusively on SYSTEM-context runtime, not HTMX/Alpine browser reactivity. Template-level correctness (element IDs, conditional rendering, href targets, command string content) is fully automated via pytest. The minor cosmetic "Uninstall copy" vs "Copy" label inconsistency flagged in 05-VERIFICATION.md Anti-Patterns is a UX polish item, not a correctness defect, and does not affect the success criterion. |
Audit outcome: 5/5 rows pass. No fail-fix-v1.1, deferred-v1.2, or wont-do rows. Phase 5 is Nyquist-compliant. Row 2 (PKG-02) is the only row in the entire 7-phase v1.0 Nyquist audit track with artifact-backed live-Intune-tenant runtime evidence — RTVAL-01 (tenant rubis.fr, 2026-04-13, screenshots committed) proves the byte-level .intunewin format is accepted by real Intune after the two structural fixes in commits 74535ea + 7716246 flipped the initial FAIL into a PASS. Row 4 (PKG-04) records the historical icon-embedding gap and its Phase 6 closure in place rather than inflating to fail-fix-v1.1. No other gaps carry forward into 08-08 rollup for Phase 5 beyond what STATE.md already tracks.
Validation Sign-Off
- All tasks have
<automated>verify or Wave 0 dependencies - Sampling continuity: no 3 consecutive tasks without automated verify
- Wave 0 covers all MISSING references
- No watch-mode flags
- Feedback latency < 10s
nyquist_compliant: trueset in frontmatter- Nyquist audit complete — 2026-04-13 — Sébastien QUEROL
Approval: Nyquist-audited 2026-04-13 by Claude (gsd-executor, plan 08-05) — 5/5 pass (PKG-02 only artifact-backed live-tenant runtime row in track); signed off 2026-04-13 by Sébastien QUEROL (index: v1.0-VALIDATION-INDEX.md)