Auditoria prática: acesso, dados e contestação

Missão, hipótese e limites

Missão: avaliar uma tela fictícia que conclui a OS 742, um inventário de dados enviado ao ERP e uma recomendação de prioridade por IA. Você começará com falhas conhecidas, obterá resultado vermelho, corrigirá e provará resultado verde.

Hipótese: requisitos estruturais, evidências manuais e documentação de governança conseguem detectar barreiras e decisões incompletas antes da produção. O validador não decide conformidade WCAG, justiça ou base legal; ele impede que campos obrigatórios e falhas deliberadas desapareçam do relatório.

Ambiente: Python 3.11 ou superior, navegador moderno, editor e terminal. Duração: 120–180 minutos. Use somente os dados sintéticos abaixo. Não inclua nome, CPF, matrícula, localização, comentário ou credencial real.

Neste laboratório, auditar não significa procurar culpados nem declarar que o produto inteiro está conforme. Significa comparar uma afirmação verificável com uma observação registrada. “A tela é acessível” é ampla demais; “o foco alcançou o botão com Tab, no navegador e na versão anotados” pode ser reproduzida. A saída do programa é evidência estrutural: mostra propriedades do arquivo. O relato de teclado, zoom e leitor de tela é evidência manual: mostra uma experiência executada. O inventário e o registro de contestação são evidências de processo: tornam finalidade, autoridade e pendências visíveis. Nenhuma delas substitui as outras. Quando uma verificação não puder ser feita, registre “não testado”, o motivo e o responsável pelo próximo passo; não converta ausência de evidência em aprovação.

Crie numa pasta temporária auditoria-os-742:

auditoria-os-742/
├── jornada.html
├── auditoria.json
├── validar_auditoria.py
└── evidencias.md

Em evidencias.md, registre versão do Python, navegador, zoom, tecnologia assistiva usada, comandos, saída vermelha, saída verde, mutações, limitações e conclusão. Se não houver leitor de tela disponível, declare a limitação; não marque “passou” sem executar.

Passo 1 — construa a falha deliberada

Salve como jornada.html:

<!doctype html>
<html>
  <head><meta charset="utf-8"><title>OS 742</title></head>
  <body>
    <h1>Concluir OS 742</h1>
    <textarea id="notes"></textarea>
    <div class="button">Concluir ordem</div>
    <div id="result">Aguardando</div>
  </body>
</html>

Há quatro problemas intencionais: idioma da página ausente; campo sem rótulo associado; div que parece botão, mas não é controle nativo; e mensagem sem região de status para tecnologia assistiva.

Salve como auditoria.json. JSON é um formato textual para organizar dados em pares como "campo": "valor"; aqui ele funciona como o formulário estruturado da auditoria:

{
  "journey": {
    "keyboard_completed": false,
    "keyboard_evidence": "botao nao recebe foco",
    "zoom_200_completed": false,
    "zoom_200_evidence": "nao testado",
    "screen_reader_feedback": false,
    "screen_reader_evidence": "resultado nao anunciado"
  },
  "data_inventory": [
    {
      "field": "technician_name",
      "purpose": "atribuir a ordem",
      "necessary": true,
      "access_roles": ["despacho", "supervisao"],
      "retention_days": 365,
      "legal_basis_status": "a validar",
      "legal_review_owner": "encarregado simulado"
    },
    {
      "field": "exact_gps",
      "purpose": "",
      "necessary": false,
      "access_roles": ["todos"],
      "retention_days": 0,
      "legal_basis_status": "desconhecida",
      "legal_review_owner": ""
    }
  ],
  "automated_decision": {
    "purpose": "recomendar prioridade da OS",
    "ai_disclosed": false,
    "human_can_change": false,
    "appeal_channel": "",
    "response_days": 0,
    "group_review_documented": false,
    "known_limitations": []
  }
}

Os valores são didáticos, não política de retenção ou conclusão jurídica. a validar registra pendência; não significa autorização de tratamento.

Passo 2 — use um validador estreito e explicável

Salve como validar_auditoria.py:

import json
import sys
from html.parser import HTMLParser
from pathlib import Path


class StructureAudit(HTMLParser):
    def __init__(self):
        super().__init__()
        self.has_lang = False
        self.buttons = 0
        self.control_ids = set()
        self.label_targets = set()
        self.status_regions = 0

    def handle_starttag(self, tag, attrs):
        values = dict(attrs)
        if tag == "html" and values.get("lang"):
            self.has_lang = True
        if tag == "button":
            self.buttons += 1
        if tag in {"input", "textarea", "select"} and values.get("id"):
            self.control_ids.add(values["id"])
        if tag == "label" and values.get("for"):
            self.label_targets.add(values["for"])
        if values.get("role") == "status" or values.get("aria-live"):
            self.status_regions += 1


def validate(html_path="jornada.html", audit_path="auditoria.json"):
    findings = []
    parser = StructureAudit()
    parser.feed(Path(html_path).read_text(encoding="utf-8"))
    data = json.loads(Path(audit_path).read_text(encoding="utf-8"))

    if not parser.has_lang:
        findings.append("HTML: idioma da pagina ausente")
    if parser.buttons < 1:
        findings.append("HTML: nenhuma acao usa button nativo")
    if not parser.control_ids.issubset(parser.label_targets):
        findings.append("HTML: existe controle sem label associado")
    if parser.status_regions < 1:
        findings.append("HTML: feedback sem regiao de status")

    journey = data.get("journey", {})
    for key in ("keyboard", "zoom_200", "screen_reader_feedback"):
        completed_key = f"{key}_completed" if key != "screen_reader_feedback" else key
        evidence_key = f"{key}_evidence" if key != "screen_reader_feedback" else "screen_reader_evidence"
        if journey.get(completed_key) is not True:
            findings.append(f"JORNADA: {key} nao concluido")
        if not str(journey.get(evidence_key, "")).strip():
            findings.append(f"JORNADA: {key} sem evidencia")

    inventory = data.get("data_inventory", [])
    if not inventory:
        findings.append("DADOS: inventario vazio")
    for item in inventory:
        field = item.get("field", "sem_nome")
        if not str(item.get("purpose", "")).strip():
            findings.append(f"DADOS: {field} sem finalidade")
        if item.get("necessary") is not True:
            findings.append(f"DADOS: {field} nao necessario deve ser removido")
        if not item.get("access_roles"):
            findings.append(f"DADOS: {field} sem papeis de acesso")
        if not isinstance(item.get("retention_days"), int) or item["retention_days"] <= 0:
            findings.append(f"DADOS: {field} sem retencao positiva no cenario")
        if not str(item.get("legal_review_owner", "")).strip():
            findings.append(f"DADOS: {field} sem responsavel por revisao legal")

    decision = data.get("automated_decision", {})
    if decision.get("ai_disclosed") is not True:
        findings.append("IA: participacao automatizada nao informada")
    if decision.get("human_can_change") is not True:
        findings.append("IA: revisor humano sem autoridade")
    if not str(decision.get("appeal_channel", "")).strip():
        findings.append("IA: canal de contestacao ausente")
    if not 1 <= decision.get("response_days", 0) <= 30:
        findings.append("IA: prazo de resposta ausente ou fora do exercicio")
    if decision.get("group_review_documented") is not True:
        findings.append("IA: revisao de erros por grupo/contexto nao documentada")
    if not decision.get("known_limitations"):
        findings.append("IA: limitacoes conhecidas ausentes")

    for finding in findings:
        print("FAIL:", finding)
    if findings:
        print(f"RESULTADO: REPROVADO ({len(findings)} achados)")
        return 1
    print("RESULTADO: APROVADO NO ESCOPO ESTRUTURAL DO LABORATORIO")
    return 0


if __name__ == "__main__":
    raise SystemExit(validate())

O analisador de HTML registra apenas cinco sinais estruturais. A função validate compara esses sinais e o JSON com o contrato do exercício. Cada achado informa camada e causa. O programa não calcula contraste, não opera teclado, não executa leitor de tela e não interpreta a LGPD; por isso exige evidência manual e declara seu limite.

Execute:

python validar_auditoria.py

Resultado esperado: código de saída 1 e várias linhas FAIL. Se aparecer aprovado, confirme que está na pasta certa e que não substituiu os arquivos iniciais.

Passo 3 — realize a auditoria humana

Abra jornada.html no navegador. Sem mouse, pressione Tab, Shift+Tab, Enter e Espaço: a ação não recebe foco. Aumente o zoom para 200%; registre se conteúdo continua visível, sem sobreposição ou rolagem bidimensional desnecessária. Use o leitor de tela disponível e verifique nome do campo, papel da ação e anúncio do resultado.

Não marque uma etapa pelo resultado desejado. Registre navegador, versão, sequência de teclas, observado e limitação. A orientação oficial do W3C sobre avaliação explica por que ferramenta e avaliação humana se complementam.

Passo 4 — corrija interface, dados e autoridade

Substitua jornada.html por:

<!doctype html>
<html lang="pt-BR">
  <head><meta charset="utf-8"><title>Concluir OS 742</title></head>
  <body>
    <main>
      <h1>Concluir OS 742</h1>
      <label for="notes">Observações técnicas</label>
      <textarea id="notes"></textarea>
      <button type="button">Concluir ordem</button>
      <div id="result" role="status" aria-live="polite">Aguardando</div>
    </main>
  </body>
</html>

O botão nativo recebe semântica e operação de teclado do navegador. O rótulo aponta para o campo. role="status" e aria-live permitem anunciar atualização; ainda será necessário testar comportamento real quando houver JavaScript.

Substitua auditoria.json pelo artefato corrigido:

{
  "journey": {
    "keyboard_completed": true,
    "keyboard_evidence": "Tab alcancou o button; Enter e Espaco foram testados no prototipo",
    "zoom_200_completed": true,
    "zoom_200_evidence": "conteudo permaneceu legivel no navegador registrado",
    "screen_reader_feedback": true,
    "screen_reader_evidence": "campo, button e regiao de status foram anunciados no teste registrado"
  },
  "data_inventory": [
    {
      "field": "technician_id",
      "purpose": "atribuir a ordem ao tecnico autorizado",
      "necessary": true,
      "access_roles": ["despacho", "supervisao"],
      "retention_days": 365,
      "legal_basis_status": "nao determinada neste laboratorio",
      "legal_review_owner": "encarregado de dados simulado"
    },
    {
      "field": "facility_code",
      "purpose": "rotear a ordem para a unidade correta sem GPS exato",
      "necessary": true,
      "access_roles": ["despacho", "manutencao"],
      "retention_days": 365,
      "legal_basis_status": "nao determinada neste laboratorio",
      "legal_review_owner": "encarregado de dados simulado"
    }
  ],
  "automated_decision": {
    "purpose": "recomendar prioridade da OS, sem concluir ou cancelar automaticamente",
    "ai_disclosed": true,
    "human_can_change": true,
    "appeal_channel": "/revisao-prioridade",
    "response_days": 2,
    "group_review_documented": true,
    "known_limitations": [
      "sensores antigos possuem mais dados ausentes",
      "recomendacao se abstém quando faltam sinais essenciais"
    ]
  }
}

O GPS exato foi removido porque não havia finalidade no cenário; facility_code oferece precisão menor para roteamento. Os 365 dias são hipótese didática e precisam de política real. nao determinada neste laboratorio impede que o exercício finja parecer jurídico. Antes de produção, o responsável valida base, prazo, acesso, compartilhamento e direitos.

Execute novamente. Esperado:

RESULTADO: APROVADO NO ESCOPO ESTRUTURAL DO LABORATORIO

Código de saída deve ser 0.

Passo 5 — provoque regressões

Primeira mutação: troque temporariamente <button type="button"> por <div>. Execute; espere HTML: nenhuma acao usa button nativo. Restaure e confirme verde.

Segunda mutação: mude human_can_change para false. Execute; espere IA: revisor humano sem autoridade. Restaure e confirme verde. Se uma mutação não falhar, o validador não protege o critério anunciado.

Esses testes não provam que toda decisão humana será boa. Provam que o artefato não pode declarar supervisão enquanto registra ausência de autoridade.

Diagnóstico e recuperação

Sintoma Hipótese Verificação Ação
FileNotFoundError pasta ou nome errado Get-Location e lista de arquivos execute na pasta temporária correta
JSONDecodeError vírgula ou aspas inválidas linha indicada pelo erro corrija sintaxe sem apagar o campo
aprovado sem teste manual booleanos copiados como verdade leia evidencias.md volte para false até executar
ferramenta automática e teclado discordam limite da automação reproduza sequência de teclas priorize bloqueio observado e investigue
campo possui “finalidade” vaga justificativa criada depois da coleta tente removê-lo reduza ou elimine; escale decisão
base legal continua pendente laboratório não é análise jurídica veja responsável e status não libere produção; encaminhe responsável

Pacote de evidências e aceite

O pacote final contém os três arquivos, evidencias.md, saída vermelha, saída verde e duas mutações falhando. Registre ainda stakeholder afetado, impacto, correção, critério WCAG relacionado e teste de regressão. Priorize incapacidade de concluir a jornada, não quantidade bruta de avisos.

O laboratório passa quando:

  • teclado, zoom e tecnologia assistiva têm evidência honesta ou limitação explícita;
  • estrutura mínima passa e falha diante das mutações;
  • localização exata sem finalidade foi removida;
  • cada dado restante possui finalidade, necessidade, acesso, retenção hipotética e responsável;
  • pendência jurídica permanece visível e bloqueia produção até validação competente;
  • participação da IA é informada, limitações estão documentadas e há abstenção;
  • contestação possui canal, prazo e revisor capaz de alterar o resultado;
  • outra pessoa reproduz comandos e conclusões sem dados reais.

Transferência: adapte o inventário a um chamado de recursos humanos ou atendimento ao cliente. Quais stakeholders, danos e dados mudam? Quais critérios — finalidade, acesso, evidência, contestação — permanecem?

Recuperação ativa: por que o validador não certifica WCAG? O que a saída verde prova? Por que a validar é melhor que base inventada? Qual mutação demonstra supervisão simbólica? Como minimização mudou GPS para código de unidade?

Conclusão e limpeza

A conclusão correta não é “o sistema é ético” nem “está em conformidade legal”. É: os controles estruturais deste cenário passaram; a jornada manual possui as evidências declaradas; uma coleta sem finalidade foi removida; a supervisão e o recurso foram documentados; e decisões jurídicas, testes completos de acessibilidade e avaliação contínua de vieses permanecem com responsáveis identificados.

Para limpar, preserve evidencias.md sem dados reais se fizer parte do portfólio. Depois remova somente a pasta temporária auditoria-os-742 que você criou e cujo caminho confirmou. Não use caminho amplo, glob ou variável não resolvida.

Teste de fixação

Comprove o que você aprendeu

Responda todas as questões. O gabarito comentado só aparece depois do envio.

1. Durante a auditoria, o teclado não alcança o botão que conclui a tarefa. Como priorizar o achado?
2. Qual pergunta operacional ajuda a aplicar minimização a cada campo inventariado?
3. Um usuário encontra um canal de recurso, mas a revisão apenas repete a saída do modelo e não pode alterá-la. Qual falha foi demonstrada?

Consulta universal

O que você quer encontrar?

Títulos, capítulos, conceitos, termos, laboratórios e ferramentas em uma única busca.

Digite pelo menos dois caracteres.