Files
GTI_GERENCIADOR_FISCAL/docs/superpowers/plans/2026-09-23-windows-installer-exe-v1.md
T

6.6 KiB

GTI FISCAL WINDOWS INSTALLER EXE V1 — Implementation Plan

  • Date: 2026-09-23
  • Spec: docs/superpowers/specs/2026-09-23-windows-installer-exe-v1-design.md
  • Branch strategy: work on master in focused commits (repo convention; no remote).

Order of work (TDD front-loaded)

Phase A — Backend install_code (code-first, tests first)

  1. central/app/enrollment.py: add Crockford Base32 install-code module (generate_install_code, normalize_install_code, install_code_digest, valid_install_code). 16 chars, alphabet 0123456789ABCDEFGHJKMNPQRSTVWXYZ, grouped XXXX-XXXX-XXXX-XXXX.
  2. Migration 0023_install_codes.py on agent_enrollments: install_code_hash, install_code_expires_at, install_code_attempts, install_code_created_at. Update AgentEnrollment model.
  3. central/app/agent_distribution.py — POST /v1/agents/enroll:
    • Accept install_code (primary) OR legacy (installation_id + enrollment_token).
    • If both bodies present → 400 INVALID_REQUEST (ENROLLMENT_REJECTED).
    • Install-code path resolves Installation by hash alone; validates status, expiry, one-time, attempts, release PUBLISHED; mints agent+installation, marks code used (hash cleared after success → replay-proof), audits ENROLLMENT_STARTED/SUCCEEDED/REJECTED (never the code).
    • Rate limit: keep per-IP enroll bucket AND per-code/installation bucket (add record_attempt("enroll_code:<enrollment_uuid>", ip)).
    • install_code_attempts increments on each failed attempt.
  4. central/app/web.py + templates:
    • agent_pending_detail.html / agent_detail.html: Código de instalação XXXX-XXXX-XXXX-XXXX + [Copiar], Expira em, [Baixar instalador (.EXE)], [Gerar novo código], [Revogar]. No raw tokens.
    • POST /agents/pending/{uuid}/code → generate/rotate code (audit INSTALLATION_CODE_CREATED); POST /agents/pending/{uuid}/revoke already exists → also clear code + audit INSTALLATION_CODE_REVOKED.
    • agent_releases.html main button → EXE.
  5. Downloads serve universal EXE: build_and_serve → when release artifact is an EXE, serve the stored EXE bytes (no bundle zip / no enrollment.json for the EXE flow). Filename for EXE = stored release filename. Audit INSTALLER_DOWNLOADED.
  6. central/scripts/publish_agent_release.py: generalize --zip to --artifact (accept .zip or .exe); filename derivation handles GTIFiscalAgent-Setup-<v>.exe; enforce immutability (refuse replacing a PUBLISHED artifact). published_at fix already in place (commit 21c9261).
  7. Tests in central/tests/test_agent_distribution.py + new test_install_codes.py (RED→GREEN). Full suite green.

Phase B — Agent runtime credential isolation 8. agent/src/gti_fiscal_agent/credential_store.py: DPAPI LocalMachine encrypt/decrypt (win32crypt.CryptProtectData/CryptUnprotectData with CRYPTPROTECT_LOCAL_MACHINE), ACL enforcing (SYSTEM/Administrators), plaintext-token fallback only in dev (env GTI_ALLOW_PLAINTEXT_CREDENTIAL=1). 9. run_agent_service.py Config: read AGENT_TOKEN from env OR decrypt from credentials.bin (GTI_CREDENTIAL_FILE, default Program Files path); keep agent.env non-secret; enforce FIREBIRD_WRITES=0 (already fail-closed). 10. Tests: credential_store round-trip + no-write enforcement.

Phase C — Installer (Windows build host = this dev machine) 11. installer/ package (self-contained sources bundled by PyInstaller): - installer/setup_main.py (uac-admin entitlement via PyInstaller --uac-admin): tkinter UI with single code field; fixed prod Hub URL (dev override only via GTI_HUB_URL env/CLI); POST enroll; write agent.env (non-secret) + credentials.bin (DPAPI LocalMachine + ACL); extract bundled Service host + Uninstall.exe into Program Files; register service + recovery sc.exe failure ...; report preflight; write ARP key; best-effort WINDOWS_INSTALL_COMPLETED. - installer/service_main.py: minimal Windows Service host entry reusing gti_agent_windows_service logic (token from credential_store). - installer/uninstall_main.py: stop/remove service, remove Program Files tree + ARP, remove credentials. 12. scripts/build_windows_installer.ps1: clean dist/installer, read release/VERSION (1.1.1), PyInstaller builds (Setup onefile --uac-admin, service host onedir, uninstall onefile), embed service+uninstaller as data in Setup, write .sha256. Fails on missing tool/dep. Reproducibility = automated/repeatable + immutable release (not byte-identical hashes). 13. Local build + smoke: produce EXE; validate layout, sha256, no credential in agent.env, FIREBIRD_WRITES=0.

Phase D — Local E2E (this Windows dev machine) 14. Run Setup against a LOCAL Hub (control-plane image, scratch postgres or SQLite test env) for the UI/install flow: invalid code → clear error; valid code → enroll → verify utils/env/credential/service registration; reinstall idempotence; uninstall. (Real production E2E happens after deploy, Phase F.)

Phase E — Production deploy + release 15. Backup: pg_dump of gti_fiscal + release storage volume copy (via container) to backup location; validate restore (EXE + metadata) from the backup copy. 16. Build + deploy Hub image (migration 0023 via alembic upgrade head); postgres intact; smoke: health, panel, downloads. 17. Publish release 1.1.1 EXE (immutable), AGEING NOT overwritten: 1.1.0 zip untouched.

Phase F — Production E2E (REAL Windows) 18. Create E2E installation in production panel; generate code; download real EXE from https://gtifiscal.gtigrupo.com.br; run on this real Windows host, enter code, enroll. Verify Installation→ENROLLED, Agent created, credentials.bin DPAPI-protected (ACL), agent.env no token, service registered (Automatic/Delayed, recovery, NOT started since Firebird blank), WINDOWS_INSTALL_COMPLETED audit. 19. Rejection E2E: invalid code, expired code, replayed/used code, code+token together → 400. Each audited. 20. Uninstall via Uninstall.exe; revoke/deactivate E2E agent in panel; audit preserved.

Phase G — Report 21. Final report in the mission's exact field list + COMMITS + GIT_STATUS.

Verification gates

  • Entire central/tests suite green: python -m pytest central/tests -q and python -m pytest tests -q (repo-level offline tests) after each phase.
  • Migration head check: 0023 newest; fresh-DB upgrade test green.
  • Artifacts: dist/installer/GTIFiscalAgent-Setup-1.1.1.exe + *.sha256.
  • No AGENT_TOKEN literal anywhere in agent.env/installed tree.