"""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'.*?\s*', '', text, flags=re.S) first, _, rest = text.partition('\n') path.write_text(first + '\n\n\n' + content.strip() + '\n\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}))