76 lines
8.3 KiB
Python
76 lines
8.3 KiB
Python
"""Append a scoped response/§27 receipt to the existing matrix; no new inventory."""
|
||
import csv
|
||
import hashlib
|
||
import json
|
||
import re
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
DOCS = ROOT / 'docs/investmoz/v2/auditoria-portal'
|
||
PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
|
||
csv.field_size_limit(16 * 1024 * 1024)
|
||
proof = json.loads((PRIVATE / 'opportunity-response-http-qa.json').read_text(encoding='utf-8-sig'))
|
||
checks = json.loads((PRIVATE / 'profile-installation-checks-qa.json').read_text(encoding='utf-8-sig'))
|
||
assert proof['result'] == checks['result'] == 'passed' and proof['active_writes'] == 0 and not proof['interface_observed']
|
||
assert len(proof['responses']) == 10 and len(proof['semantics']) == 2
|
||
schema_sha = hashlib.sha256((ROOT / 'resources/data/investmoz-opportunity-modular.schema.json').read_bytes()).hexdigest()
|
||
assert schema_sha == '9ce0acfa5df1f81a3357dee9e5a9a1c4e77e4eefc8bce294497d18f4caa8e5c0'
|
||
matrix = DOCS / 'matriz-requisitos.csv'
|
||
with matrix.open(encoding='utf-8-sig', newline='') as source:
|
||
rows = list(csv.DictReader(source))
|
||
protected = ['ID', 'Pedido/trecho', 'Fonte/ocorrências', 'Critério de aceitação', 'Versões divergentes', 'IDs das linhas de origem', 'Quantidade de linhas de origem', 'Critérios e versões por origem (JSON)', 'Estado']
|
||
before = [{k: r[k] for k in protected} for r in rows]
|
||
tag = '[Response/§27 QA 10/10/2026] '
|
||
values = {
|
||
'Implementação encontrada': 'Resposta de leitura versionada com IDs bigint/referências legados intactos, recursos UUID hidratados e metadados efetivamente gravados. Integra o editor existente e GET /minhas-oportunidades/{id}/response. Succession aceite na venda de negócio conforme contrato original; §27 recusa cadastro de parcela repetido na mesma oportunidade antes de submeter.',
|
||
'Evidência funcional': 'opportunity-response-http-qa.json: dez respostas recuperadas noutra sessão MFA e iguais ao consumidor Inertia; Validador permitido, organização alheia 403 e sessão anónima 401. QA40: rascunho duplicado recuperável, submissão 422 no campo cadastral, correção e submissão aceite. QA41: succession guardada, submetida e recuperada. Testes isolados específicos em OpportunityCanonicalWorkflowTest; nenhum ensaio visual.',
|
||
'Lacuna': 'Response normativo integral ainda parcial: algoritmo de completude, identidade/metadados de itens inline, correspondência explícita de estados e autor de alteração/exclusão de recursos. §27 conservado como fonte, sem criar tabelas especializadas paralelas. Quatro perfis e SLA não aprovados; condições de unidades/urbanismo/entidade sem exercícios mantêm decisões próprias.',
|
||
'API': 'GET privado /minhas-oportunidades/{id}/response; recurso autorizado antes de hidratar, Cache-Control private/no-store. Escritas existentes e validações 422 preservadas; nunca publica nem aprova por leitura.',
|
||
'Gravação': 'QA40 e QA41 sintéticos, usando oportunidades e coleções existentes. Nenhuma alteração das dez oportunidades anteriores, titulares, prazos ou base ativa.',
|
||
'Recuperação': 'Novas sessões Fortify/MFA recuperam IDs, referências, valores e metadados. Números cadastrais ausentes permanecem desconhecidos; sem renumerar UUIDs/bigints ou fabricar aprovação/completude.',
|
||
'Interface': 'Resposta entregue ao editor Inertia existente; interface, C37, Promotor QA21 e A/B continuam pendentes. Não confundir recuperação das props com clique ou render observado.'
|
||
}
|
||
for row in rows:
|
||
if row['ID'] == 'AUD-OP-04':
|
||
for key, value in values.items():
|
||
old = re.sub(r'\s*\|\s*' + re.escape(tag) + r'.*?(?=\s*\|\s*\[|$)', '', row[key], flags=re.S)
|
||
row[key] = old + ' | ' + tag + value
|
||
assert before == [{k: r[k] for k in protected} for r in rows]
|
||
assert len(rows) == 3386 and sum(int(r['Quantidade de linhas de origem']) for r in rows) == 6838
|
||
with matrix.open('w', encoding='utf-8-sig', newline='') as f:
|
||
writer = csv.DictWriter(f, fieldnames=list(rows[0])); writer.writeheader(); writer.writerows(rows)
|
||
|
||
marker = 'qa-response-20261010'
|
||
def prepend(path, content):
|
||
text = path.read_text(encoding='utf-8-sig')
|
||
text = re.sub(r'<!-- ' + marker + r':begin -->.*?<!-- ' + marker + r':end -->\s*', '', text, flags=re.S)
|
||
first, _, rest = text.partition('\n')
|
||
path.write_text(first + '\n\n<!-- ' + marker + ':begin -->\n' + content.strip() + '\n<!-- ' + marker + ':end -->\n\n' + rest.lstrip(), encoding='utf-8')
|
||
|
||
summary = f'''## Incremento atual — Response/§27 e propostas para decisão
|
||
|
||
Somente QA **http://127.0.0.1:18097**, `{proof['target']}`. Mesmo inventário/matriz; nenhum fecho integral ou escrita ativa.
|
||
|
||
- **AUD-OP-04:** `/minhas-oportunidades/{{id}}/response` integra a leitura do editor existente. Preserva IDs, códigos, referências UUID e estados atuais. Dez casos #30–39 recuperados noutra sessão MFA; Validador permitido, organização alheia 403 e anónimo 401. A versão do adaptador declara expressamente que a resposta normativa completa continua parcial.
|
||
- **C65 / QA40:** §27 exige unicidade de número cadastral por oportunidade. Rascunho incompleto/duplicado permanece recuperável; submissão devolve 422 junto a `canonical.land_rights.parcels.1.cadastral_number`. Após correção, submissão aceite. Números ausentes não são inventados.
|
||
- **C66 / QA41:** `succession` constava no contrato original mas era recusada pela lista semântica de venda. Lista reconciliada; criação/edição, submissão e nova sessão demonstradas. Não alterar silenciosamente outras modalidades.
|
||
- **Ainda parcial:** algoritmo de completude, metadados/identidade de itens inline, equivalência explícita dos estados e recuperação dos autores de alteração/exclusão dos recursos. §27 não cria tabelas paralelas. Condições sem regra inequívoca continuam como decisões D-S1/S2.
|
||
|
||
Provas: `opportunity-response-http-qa.json`, `OpportunityCanonicalWorkflowTest`, `profile-installation-checks-qa.json` e schema original preservado. O recibo identifica separadamente a tentativa anterior em que passaram os dez critérios de leitura e o ensaio semântico posterior; falhas do harness foram conservadas, não apresentadas como falhas do portal.
|
||
|
||
**Propostas modulares e SLA continuam não aprovados.** O ficheiro `propostas-perfis-modulares-sla.md` explicita compromissos internos de atendimento, janela operacional do Promotor, pausas por tarefa, espera externa separada e ausência de decisão automática. Nenhum `due_at`, titular ou perfil modular ativado.
|
||
|
||
**Ativação:** o restauro reportado é QA. Para uma futura ativação, conferir a identidade, schema, dados e configuração efetivos da base ativa para a revisão candidata, capturar hashes/manifesto dessa revisão e provar o seu backup/restauro. As provas QA ou `ready=true` não substituem essa ligação. Sequência 030000 → 040000 → 190000; C37, QA21 e A/B visual pendentes, candidata não fixada.
|
||
|
||
**Próximo trabalho independente:** completar metadados/Response sem novos cadastros; PDFs gerados/exportações/derivadas por consumidor; países/origem com filtros e estatísticas por formulário; correspondências inequívocas, mantendo Mandie/Guro ambíguo. Cartografia permanece 408 resolvidas e 173 lacunas neste incremento.
|
||
'''
|
||
for name in ['relatorio-executivo.md', 'backlog.md', 'workflow-oportunidades-qa.md']:
|
||
prepend(DOCS / name, summary)
|
||
prepend(DOCS / 'plano-ativacao.md', '**Preparação da futura ativação.**' + summary.split('**Ativação:**', 1)[1].split('**Próximo trabalho independente:**', 1)[0])
|
||
case_block = '- **C65' + summary.split('- **C65', 1)[1].split('- **Ainda parcial:**', 1)[0]
|
||
prepend(DOCS / 'casos-reproducao.md', case_block + '\nC65 foi identificado por confronto código/§27; C66 por divergência da lista do servidor com o contrato. A prova atual é pós-correção. Não atribuir a estes casos falhas antigas não reproduzidas.')
|
||
prepend(ROOT / 'docs/investmoz/v2/gestao-oportunidades-dashboard.md', summary)
|
||
(PRIVATE / 'response-audit-update-qa.json').write_text(json.dumps({'requirements': 3386, 'source_occurrences': 6838, 'protected_fields_preserved': True,
|
||
'schema_sha256': schema_sha, 'states_unchanged': True, 'updated': ['AUD-OP-04'], 'active_writes': 0}), encoding='utf-8')
|
||
print(json.dumps({'requirements': 3386, 'source_occurrences': 6838, 'updated': ['AUD-OP-04'], 'states_unchanged': True, 'active_writes': 0}))
|