# Task 3 Report: Safe Blocker Resolution Workflow ## Status Implemented Task 3 only. The old hidden-context blocker action was replaced by a structured, server-rendered, CSRF-protected resolution workflow. The JSON control-panel route now uses the same validated transaction service and rejects arbitrary context text. ## RED Created `central/tests/test_blocker_resolution_panel.py` before production implementation. Initial focused command: ```text python -m pytest central/tests/test_blocker_resolution_panel.py -q ``` Expected RED result: ```text ModuleNotFoundError: No module named 'central.app.blocker_resolution' ``` The failure occurred during collection because the required production module did not yet exist. ## Implementation - Added `central/app/blocker_resolution.py` with structured input validation through `validate_entry`, workflow-scope restrictions, exact PRODUCT scope-key enforcement, persisted installation/company context derivation, context upsert, bounded `REPROCESS_PRODUCTS` job creation, sanitized audit recording, and one commit with rollback on failure. - Added `GET /blockers/{blocker_id}` with installation-aware blocker detail and context form. - Added `POST /blockers/{blocker_id}/resolve-context` with human authentication and CSRF enforcement. - Updated `/v1/blockers/{blocker_id}/resolve-human` and added `/v1/blockers/{blocker_id}/resolve-context` to call the same service; neither accepts arbitrary context text. - Removed the blocker-list hidden fixed-context form and `data-confirm` action. - Added focused coverage for enum validation, unknown keys, unsupported NCM scope, empty reason, missing PRODUCT scope key, authentication, CSRF, context/job/audit persistence, exact product scoping, and secret redaction. ## GREEN Focused command: ```text python -m pytest central/tests/test_blocker_resolution_panel.py -q ``` Result: ```text 5 passed, 1 warning ``` Central suite: ```text python -m pytest central/tests -q ``` Result: ```text 261 passed, 1 warning ``` The warnings are existing third-party test-environment warnings from `pytest_asyncio` configuration and `openpyxl` print-area parsing. ## Safety Review - No Firebird, AI, SQL execution, shell, credential, or remote-write path was added. - The workflow accepts only `scope`, `scope_key`, `key`, `value`, and `reason`; context keys and enum values are validated against existing shared rules. - Company context is derived from persisted blocker/installation data, not arbitrary client `company_key` input. - Jobs contain exactly the blocked product code. - Audit context passes through existing redaction before persistence. ## Concerns - Live PostgreSQL and agent execution were not exercised; verification used the repository's offline SQLite test fixtures. - The blocker remains open until the agent's normal reprocess completion path reports resolution; this prevents marking it resolved before the bounded job succeeds. ## Review Fixes ### RED Added regression tests before changing production code for the four review findings: - Legacy blockers with `installation_id=NULL` must fail closed without selecting an Agent or creating work. - Cross-tenant installation and Agent pointers must fail before context, job, or audit mutation. - Oversized values/reasons and non-string fields must be rejected cleanly. - Successful web resolution must redirect to the blocker detail page. Focused command before the fixes: ```text python -m pytest central/tests/test_blocker_resolution_panel.py -q ``` RED result: ```text 4 failed, 5 passed ``` The failures reproduced the legacy Agent fallback, cross-tenant acceptance, unbounded/malformed input, and `/blockers` redirect behavior. ### GREEN - Resolution now requires a non-null exact installation and verifies both installation and Agent tenant ownership before mutation. - `value` is required to be a string of at most 512 characters; `reason` is required to be a string of at most 2000 characters. - Boundary-sized audit data retains all required audit fields without whole-context truncation. - Web success redirects to `/blockers/{blocker_id}`. Focused command: ```text python -m pytest central/tests/test_blocker_resolution_panel.py -q ``` Result: ```text 9 passed, 1 warning ``` Full central command: ```text python -m pytest central/tests -q ``` Result: ```text 265 passed, 1 warning ``` The existing `pytest_asyncio` configuration and `openpyxl` print-area warnings remain unchanged. ## UTF-8 Byte-Length Re-Review Fix ### RED Added `test_resolution_input_rejects_multibyte_utf8_at_ascii_character_limits` before changing production code. It submits `é` repeated 512 times as `value` and 2000 times as `reason`; both are within the existing character counts but exceed the corresponding UTF-8 byte budgets. Focused command: ```text python -m pytest central/tests/test_blocker_resolution_panel.py -q ``` RED result: ```text 1 failed, 9 passed ``` The failure confirmed character-count validation accepted the multibyte inputs. ### GREEN - `value` and `reason` limits now use `len(value.encode("utf-8"))` and `len(reason.encode("utf-8"))`. - Existing ASCII boundary inputs remain accepted. - Multibyte inputs fail closed with explicit UTF-8 byte-limit validation errors, before persistence or audit sanitization. - Redaction and remote-write boundaries were not changed. Focused result: ```text 10 passed, 1 warning ``` Full central result: ```text 266 passed, 1 warning ``` The existing `pytest_asyncio` configuration and `openpyxl` print-area warnings remain unchanged.