Files
GTI_GERENCIADOR_FISCAL/docs/superpowers/plans/2026-09-22-control-panel-operational-ux-v1.md
T

19 KiB

Control Panel Operational UX V1 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Turn the existing server-rendered Control Panel into a bounded operational queue that identifies client/server problems, presents distinct technical/fiscal metrics, and offers safe audited context resolution.

Architecture: Keep FastAPI, Jinja2, project CSS, and minimal JavaScript. Extend control_plane.py with bounded query/view-model functions and add a focused panel_presentation.py for timestamps, duration, labels, priorities, and attention ordering. Use existing FiscalContextEntry, Job, AuditEvent, CSRF, and human authorization; do not add remote write capabilities or fiscal-engine changes.

Tech Stack: Python 3.12, FastAPI, Jinja2, SQLAlchemy, PostgreSQL/SQLite tests, pytest, vanilla CSS/JavaScript.

Global Constraints

  • Keep FastAPI, Jinja2, CSS próprio, and JavaScript mínimo; do not introduce React, Vue, Node build chain, or SPA.
  • Do not alter fiscal logic, requiredness, Hub/Agent protocol, Firebird writes, AI writes, deployment, Proxmox, or real Agents.
  • safe_blocked_products means distinct active tenant/product pairs; open_blockers means active blocker rows.
  • Enforce agents_total == online + degraded + offline + failed + unknown in code and tests.
  • UI timezone is configured by UI_TIMEZONE, default America/Bahia; storage/API timestamps remain UTC.
  • Every mutating human action requires human auth, CSRF, deterministic validation, and an AuditEvent.
  • Weak local passwords are allowed only with ALLOW_WEAK_DEV_PASSWORD=1 outside production; production requires the strong policy.
  • Never log or commit credentials, session tokens, passwords, SQL, shell commands, or raw connection strings.
  • Preserve bounded pagination and avoid N+1 queries on 100-installation fixtures.

Task 1: Presentation Helpers And Password Policy

Files:

  • Create: central/app/panel_presentation.py
  • Modify: central/app/config.py:16-39
  • Modify: central/app/human_auth.py:create_admin
  • Modify: central/tests/test_control_plane.py
  • Create: central/tests/test_panel_presentation.py
  • Modify: docker-compose.yml to explicitly enable weak development passwords only for the local service

Interfaces:

  • Produces format_ui_datetime(value) -> str, format_age(value, now=None) -> str, format_duration(started_at, finished_at, now=None) -> str, operational_priority(blocker) -> str, and health_label(value) -> str.

  • format_ui_datetime returns - for no value, a localized dd/mm/YYYY HH:MM string, and accepts UTC-naive values as UTC.

  • format_age returns -, agora, há 1 min, há N min, há 1 h, or há N h; future values return agora.

  • format_duration returns - when no start exists, em andamento when only started exists, and compact seconds/minutes/hours when both exist.

  • create_admin accepts short passwords only when settings.environment != "production" and ALLOW_WEAK_DEV_PASSWORD=1; otherwise it retains the strong minimum.

  • Step 1: Write failing timestamp, duration, priority, and password-policy tests

def test_ui_times_use_configured_timezone_and_human_age(monkeypatch):
    monkeypatch.setenv("UI_TIMEZONE", "America/Bahia")
    value = datetime(2026, 9, 22, 19, 11, tzinfo=timezone.utc)
    assert format_ui_datetime(value) == "22/09/2026 16:11"
    assert format_age(value, datetime(2026, 9, 22, 19, 19, tzinfo=timezone.utc)) == "há 8 min"

def test_duration_and_priority_are_deterministic():
    start = datetime(2026, 9, 22, 19, 0, tzinfo=timezone.utc)
    finish = start + timedelta(seconds=194)
    assert format_duration(start, finish) == "3 min 14 s"
    assert operational_priority(SimpleNamespace(blocker_type="TECHNICAL_FAILURE", field="", reason_code="X")) == "CRITICAL"

def test_short_local_password_requires_explicit_setting(monkeypatch):
    monkeypatch.delenv("ALLOW_WEAK_DEV_PASSWORD", raising=False)
    with pytest.raises(ValueError):
        create_admin(session, "admin", "admin")
    monkeypatch.setenv("ALLOW_WEAK_DEV_PASSWORD", "1")
    assert create_admin(session, "admin", "admin").username == "admin"
  • Step 2: Run the focused tests and verify they fail

Run: python -m pytest central/tests/test_panel_presentation.py central/tests/test_control_plane.py -q

Expected: FAIL because the presentation module and environment-aware password gate do not yet exist.

  • Step 3: Implement the helpers and environment-aware password check

Use zoneinfo.ZoneInfo(settings.ui_timezone) for display conversion. Add ui_timezone and allow_weak_dev_password configuration fields, but read the weak-password environment flag at call time so tests and explicit local process settings cannot be stale. Update the local Compose service with ALLOW_WEAK_DEV_PASSWORD=1; do not set it for production configuration.

  • Step 4: Run focused tests and inspect the diff

Run: python -m pytest central/tests/test_panel_presentation.py central/tests/test_control_plane.py -q and git diff --check.

Expected: PASS with no whitespace errors and no credential values in the diff.

  • Step 5: Commit the slice
git add central/app/panel_presentation.py central/app/config.py central/app/human_auth.py central/tests/test_panel_presentation.py central/tests/test_control_plane.py docker-compose.yml
git commit -m "feat: add operational panel presentation helpers"

Task 2: Bounded Operational Query View-Models

Files:

  • Modify: central/app/control_plane.py
  • Modify: central/app/models.py only if a missing relationship-free field is proven necessary; do not add a migration for presentation-only data
  • Modify: central/tests/test_control_plane.py
  • Create: central/tests/test_operational_view_models.py

Interfaces:

  • Produces dashboard_summary(session, now=None) with health_counts, agents_total, safe_blocked_products, open_blockers, open_blocker_breakdown, attention_items, failed_jobs, and outdated_agents.

  • Produces list_installations(..., sort="display_name") with fiscal_completion_percent, technical_health, fiscal_status, version_drift, formatted presentation fields, and installation_id/agent_id as secondary data.

  • Produces list_jobs(..., client=None, search=None) joining Job to Agent, Installation, and Tenant, returning client/display/hostname and duration fields.

  • Produces list_blockers(..., client=None, installation=None, status="OPEN", age=None, search=None) with summaries, priority, age, and stable pagination.

  • Produces blocker_detail(session, blocker_id) with blocker, installation identity, related events, related jobs, and resolution audit rows.

  • Step 1: Add failing tests for dashboard reconciliation and metric semantics

Seed a small SQLite fixture with five health states, two blockers for one product, one blocker for another product, and distinct clients. Assert that agents_total equals the five state sum, safe_blocked_products == 2, and open_blockers == 3.

  • Step 2: Add failing tests for attention ordering and limit

Seed more than ten installations with mixed health, required blockers, and outdated versions. Assert that the first items are FAILED, then DEGRADED, OFFLINE, UNKNOWN, and that the result length is at most ten. Assert every item contains client, display name, hostname, problem, age, and link target.

  • Step 3: Add failing tests for agents, jobs, blockers, and detail view-models

Assert agent rows expose percentage and separate technical/fiscal labels; job rows expose client/display/hostname and 3 min 14 s; blocker filters reduce the result server-side; detail rows expose evidence, related job/event IDs, and audit resolution history.

  • Step 4: Implement bounded SQL/query helpers

Use one installation join for agent/dashboard data, aggregate open blockers with count(*) and count(distinct (tenant_id, product_code)) semantics compatible with SQLite/PostgreSQL, and bulk-load related job/event/audit rows for details. Keep page size capped at 100. Map blocker fields to CFOP, ST, ICMS, CSOSN, NCM, or OTHER. Never turn safe_blocked into technical DEGRADED.

  • Step 5: Run view-model tests and verify query count does not grow per row

Run: python -m pytest central/tests/test_operational_view_models.py central/tests/test_control_plane.py -q

Expected: PASS; dashboard and list functions execute bounded joins without per-installation detail queries.

  • Step 6: Commit the slice
git add central/app/control_plane.py central/tests/test_control_plane.py central/tests/test_operational_view_models.py
git commit -m "feat: add operational control panel view models"

Task 3: Safe Blocker Resolution Workflow

Files:

  • Create: central/app/blocker_resolution.py
  • Modify: central/app/context_keys.py only to expose the existing enum choices cleanly to the form
  • Modify: central/app/control_api.py
  • Modify: central/app/web.py
  • Modify: central/tests/test_control_plane.py
  • Create: central/tests/test_blocker_resolution_panel.py

Interfaces:

  • validate_resolution_input(payload) -> normalized, error validates key, value, scope, scope_key, and reason through validate_entry, allowing only PRODUCT, GROUP, and COMPANY in this workflow.

  • resolve_blocker_with_context(session, blocker_id, admin, payload, request_ip=None) -> {"blocker_id": int, "job_id": int, "audit_id": int} performs one transaction.

  • Human routes are GET /blockers/{blocker_id} and POST /blockers/{blocker_id}/resolve-context; JSON API gains the same validated behavior without accepting arbitrary context text.

  • Step 1: Write failing validation, CSRF, authorization, and audit tests

Test valid entry_scope=INTER and product_role=RESALE; reject invalid enum values, unknown keys, unsupported NCM workflow scope, empty reason, missing product scope key, missing CSRF, unauthenticated access, and non-admin access. Assert successful resolution creates FiscalContextEntry, a REPROCESS_PRODUCTS job, and an audit context containing before/after and job ID.

  • Step 2: Run the focused tests and verify the new route/service fails

Run: python -m pytest central/tests/test_blocker_resolution_panel.py -q

Expected: FAIL because the service, detail route, and form route do not yet exist.

  • Step 3: Implement normalized validation and the transaction service

Derive company_key from the installation/company context, derive product scope key from the blocker product code, read the current matching FiscalContextEntry as before, upsert the normalized entry, create a REPROCESS_PRODUCTS job with exactly one product code, and record a sanitized audit event with admin_user_id, tenant, installation, product, blocker, context key/scope/value, reason, before, after, and reprocess_job_id. Commit once; rollback on any validation or persistence error.

  • Step 4: Implement detail and form routes

Render all required blocker evidence before the form, show select options for known enums, preserve CSRF, return a readable validation error on invalid input, redirect to the blocker detail after success, and remove the old data-confirm/hidden-context form.

  • Step 5: Run focused tests and verify security boundaries

Run: python -m pytest central/tests/test_blocker_resolution_panel.py central/tests/test_control_plane.py -q.

Expected: PASS; no route accepts AI_WRITE_ALLOWED, FIREBIRD_WRITES, SQL, shell, or arbitrary context keys.

  • Step 6: Commit the slice
git add central/app/blocker_resolution.py central/app/context_keys.py central/app/control_api.py central/app/web.py central/tests/test_blocker_resolution_panel.py central/tests/test_control_plane.py
git commit -m "feat: add audited blocker context resolution"

Task 4: Server-Rendered Operational Pages

Files:

  • Modify: central/app/web.py
  • Modify: central/templates/base.html
  • Modify: central/templates/dashboard.html
  • Modify: central/templates/agents.html
  • Modify: central/templates/agent_detail.html
  • Modify: central/templates/blockers.html
  • Create: central/templates/blocker_detail.html
  • Modify: central/templates/jobs.html
  • Modify: central/static/control-panel.css
  • Modify: central/static/control-panel.js
  • Create: central/tests/test_control_panel_pages.py

Interfaces:

  • Existing URLs remain stable: /, /agents, /agents/{agent_id}, /blockers, /jobs, /login.

  • New URL: /blockers/{blocker_id}; POST resolution is /blockers/{blocker_id}/resolve-context.

  • Query strings are server-side filters and remain in pagination links.

  • Step 1: Add failing page-render tests

Log in with the test admin and assert dashboard HTML contains all five health labels, separate Produtos bloqueados and Blockers abertos, ATENÇÃO NECESSÁRIA, Ver todos, failed-job and outdated sections, and no raw ISO timestamp. Assert agents contain client/display/hostname, fiscal percentage, and separate health/status badges. Assert jobs contain client/hostname/duration. Assert blockers contain visible filter labels, summary metrics, priority, detail links, and no window.confirm.

  • Step 2: Run page tests and verify they fail

Run: python -m pytest central/tests/test_control_panel_pages.py -q

Expected: FAIL against the current templates and routes.

  • Step 3: Implement route context and filters

Pass the view-model results and current filter values to templates. Add agent worst-coverage sort, blocker filter parameters, job status/search parameters, event filters on agent detail, and bounded pagination links. Keep all data server-rendered.

  • Step 4: Replace templates with operational Portuguese UI

Use accessible <label> elements, table <caption>/<thead> semantics, visible badges with text, empty states, detail links, technical IDs in secondary/details areas, and title attributes for full timestamps. Render health and fiscal badges separately.

  • Step 5: Update CSS and minimal JavaScript

Add responsive grid breakpoints for 1366/768/tablet widths, horizontal table scrolling, visible focus styles, sufficient contrast, status styling that does not rely on color alone, and only harmless progressive enhancement. Remove the generic confirmation click handler for blocker resolution.

  • Step 6: Run page tests and commit

Run: python -m pytest central/tests/test_control_panel_pages.py -q.

Expected: PASS.

git add central/app/web.py central/templates central/static central/tests/test_control_panel_pages.py
git commit -m "feat: refine operational control panel pages"

Task 5: Fixture Coverage, Documentation, And Regression Tests

Files:

  • Modify: central/scripts/generate_control_panel_fixture.py
  • Modify: central/tests/test_control_panel_fixture.py
  • Modify: docs/CONTROL_PANEL_V1.md
  • Create: docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md
  • Modify: central/tests/test_control_plane.py and focused test modules as needed

Interfaces:

  • The existing fixture command remains explicit and non-production-only.

  • Fixture includes all five health states, current/outdated/unknown versions, failed jobs, safe-blocked products, and multiple blockers for at least one product.

  • Step 1: Extend fixture tests before changing fixture data

Assert the fixture has multiple blockers sharing a tenant/product, failed jobs, all health/version states, and enough client/server identity fields for every operational table.

  • Step 2: Update fixture deterministically

Keep the existing 100-agent shape and return contract. Add a second blocker for selected products, varied blocker types/fields and reasons, explicit finished/started timestamps for jobs, and events carrying job/product/blocker references without secrets.

  • Step 3: Add documentation tests/contract assertions

Check the documentation names health-vs-fiscal semantics, safe-blocked-vs-open-blocker definitions, attention ordering/limit, timezone setting, resolution audit flow, and production password policy.

  • Step 4: Update both documentation files

Document Portuguese operational labels, URL/filter examples, the resolution workflow, supported context enums/scopes, no-write boundaries, fixture command, and UI_TIMEZONE/ALLOW_WEAK_DEV_PASSWORD configuration.

  • Step 5: Run all central tests and commit

Run: python -m pytest central/tests -q.

Expected: PASS with only existing non-failing warnings.

git add central/scripts/generate_control_panel_fixture.py central/tests docs/CONTROL_PANEL_V1.md docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md
git commit -m "test: cover operational panel fixture and contracts"

Task 6: Runtime, Performance, And Full Verification

Files:

  • Modify only files required by failing verification; do not add unrelated refactors.

  • Create no runtime artifacts containing credentials or session values.

  • Step 1: Run the complete offline regression

Run exactly:

python -m pytest tests -q
python -m pytest central/tests -q
python -m pytest agent/tests -q

Expected: all suites pass; record counts and non-failing warnings.

  • Step 2: Rebuild and start only the local Hub stack

Run: docker compose up -d --build fiscal-hub.

Verify PostgreSQL is healthy and GET http://localhost:8000/health returns 200. Do not deploy or publish additional ports.

  • Step 3: Run authenticated route smoke checks

Using the existing local admin without printing its password or cookies, request /, /agents, /blockers, /jobs, one agent detail, one blocker detail, and submit one valid resolution form with CSRF in a disposable/local fixture context. Verify redirects, 200 responses, no raw ISO timestamps in primary HTML, and no secret values in response/log output.

  • Step 4: Run bounded performance smoke

Against the 100-agent fixture, time dashboard, agents, blockers, jobs, and agent detail requests with a monotonic clock. Report the maximum route time in milliseconds and fail the check if any route exceeds 1000 ms locally or if a route produces an unbounded query loop.

  • Step 5: Verify safety flags and repository state

Check AI_WRITE_ALLOWED=false, FIREBIRD_AI_WRITES=0, no remote Firebird write path, no deployment command, git diff --check, git status --short, and recent commit list. Ensure no password, token, cookie, or secret appears in tracked files or generated reports.

  • Step 6: Produce the consolidated mission report

Report STATUS, all requested UX readiness flags, measured MAX_ROUTE_TIME_MS, test counts, PostgreSQL/Hub health, safety flags, commits, Git status, and a verdict that distinguishes completed behavior from any residual warning.