194 lines
26 KiB
Python
194 lines
26 KiB
Python
"""Prepare targeted CSV edits, preserve historical claims, and assemble a secret-free review package."""
|
||
from pathlib import Path
|
||
from datetime import datetime, timezone
|
||
import csv, hashlib, json, collections, sys, zipfile, shutil, re
|
||
|
||
csv.field_size_limit(50_000_000)
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
DOC = ROOT / 'docs/investmoz/v2/auditoria-portal'
|
||
OUT = ROOT / 'outputs/investmoz-country02-candidate-20261011'
|
||
PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
|
||
PACKET = PRIVATE / 'country-production-finalize-20261011'
|
||
PACKET.mkdir(exist_ok=True)
|
||
sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
|
||
read = lambda p: json.loads(p.read_text(encoding='utf-8-sig'))
|
||
write = lambda p, v: p.write_text(json.dumps(v, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
m = read(OUT / 'manifesto-candidata.json')
|
||
code, revision, build = m['code_hash'], m['delivery_revision'], m['production']['manifest_sha256']
|
||
stamp = datetime.now(timezone.utc).isoformat()
|
||
tag = '[Candidata COUNTRY02 produção 11/10/2026] '
|
||
notes = {
|
||
'AUD-COUNTRY-02': f'Candidata de código {code}, pacote {revision}, produção {build}: 22 fontes e 157 ativos fixados; 39 ativos efetivamente carregados idênticos no Chromium. C109 corrigido: evento Fortify válido regista MFA recente independentemente de040000; MFA inválido não autentica. Interface do instalador, país mestre, nova sessão e repetição sem duplicação demonstrados no clone, com sessões database e flags parciais desligadas. Backup/restauro correspondente:288tabelas/31sequências/154ficheiros,0ausentes,2MFAoriginais recuperados. Instalação/flag/bundle não ativos; ativo0organizações/0memberships. Nenhum titular/vínculo real atribuído.',
|
||
'AUD-OPS-01': f'Pacote {revision} pronto para revisão e autorização separada de COUNTRY02; code{code} substitui84a0e4a33dbf por correçãoMFA. Produção fora do ativo, fontes/ativos/hash final verificados; plataformaApache PHP8.5.11 carregada compatível com lock>=8.4.1. Restauro288tabelas/31sequências/154ficheiros ligado ao código36f7722ce5a5 e ao bundlede6d702f4e2c. QA295tem7tabelas adicionais(0300003recursos+0400004perfis),nenhuma em falta. Fluxo050000 independente,2colunas/0tabelas/0organizações/0contas,flag separada. Preparação não instalação; nenhuma ativação autorizada.',
|
||
'AUD-DOC-01': f'Manifesto anterior84a0e4a33dbf preservado como histórico; novo código{code}, entrega{revision}, compilação{build}. Relatório/backlog/plano distinguem QA demonstrada, preparação da ativação e disponibilidade ativa. Divergência288ativo/295QA explicada por sete tabelas identificadas; generalização anterior de fluxo de cadastro de organizações corrigida: endpoint geral de criação/atribuição não encontrado. A/B eC37 preservados; ofertas/competências/SLA permanecem decisões separadas.'
|
||
}
|
||
gaps = {
|
||
'AUD-COUNTRY-02': 'Resta autorização separada e aplicação ativa050000/flag/bundle. Nenhum dado real de organização é indispensável à instalação ou ao estado vazio; reutilização mestre exige organização legítima e membership autorizado. País/referência podem ser preenchidos posteriormente pelo representante noPUT existente. Criação geral de organizações/representações não tem CRUD encontrado e é uma frente posterior autorizada, sem inferência a partir de empresas privadas. Outros percursos transversais de países continuam nas respetivas linhas.',
|
||
'AUD-OPS-01': 'COUNTRY02 pronto para revisão: resta autorização ativa, revalidar alvo/hash/runtime/prova<=24h e executar somente050000ausente; mudança de flag e manifesto separadas. Outros recursos/flags/bootstraps permanecem030000→040000→09/190000com provas próprias. Ofertas/competências/SLA e designações reais não aprovados por esta candidata.',
|
||
'AUD-DOC-01': 'As alegações históricas sem prova por critério continuam abertas. Novos retratos têm fonte/revisão/data; o fecho técnico do pacote não fecha auditoria global, acessibilidade integral, dispositivos reais, módulos, ofertas ouSLA.'
|
||
}
|
||
dependencies = {
|
||
'AUD-COUNTRY-02': 'Existem organizations/memberships/audit_events/workspace. Gestor existente com email verificado e MFA recente<=15min; dois elegíveis observados por leitura, nenhum escolhido. 030000/040000/190000 não são pré-condições. Cadastro real de organização/representação necessário somente para reutilizar o mestre, não para instalar a extensão.',
|
||
'AUD-OPS-01': 'Autorização ativa separada para entrega delimitadac68ceef9d4a3; snapshots/hashs/manifesto e target investemoz:5432 atuais, prova<=24h. Nenhuma conta/fixture/.env/chaveQA no pacote. Confirmar sessão do gestor no host configuradoportal-investidor.test após autorização.',
|
||
'AUD-DOC-01': 'Preservar a matriz única, IDs/ocorrências/versões/fechos e ligar os novos recibos. Aceitação das fontes oficiais mantém-se; decisões modulares e SLA permanecem separadas.'
|
||
}
|
||
acceptance = {
|
||
'AUD-COUNTRY-02': 'QA: MFA Fortify normal sem040000, rejeição de código inválido, instalação autenticada/idempotência/auditoria, país/PT salvo e recuperado em nova sessão database,39ativos SHAiguais e390px sem extravasamento, provas anteriores de cópias/permissões/versões preservadas. Ativo após autorização:050000+2colunas,0tabelas/contas/org/vínculos,flagcountrytrue e bundlede6d702f4e2c; mestre vazio controlado, sem reescrever submissões anteriores.',
|
||
'AUD-OPS-01': 'Verificar pacote/code/bundle/alvo e revalidar prova; GETprévia/hash seguidoPOSTautenticado/CSRF/MFA/confirm/reason/UUID. Aplicar só050000; repetirUUID sem novaDDL, não executar migrateglobal. Acrescentar ativos imutáveis, trocar manifesto por substituição atómica, habilitar sóflagcountry. Reversão:flagfalse+manifestoanterior75352185c2a8, preservarcolunas/histórico/chave.',
|
||
'AUD-DOC-01': 'Relatório/plano/manifesto refletem código36f7722ce5a5 e entrega c68ceef9d4a3; matriz mantém3393IDs/3393linhas/6845ocorrências e estados, com apenas três requisitos atualizados. Expor pendências reais e separar preparação/instalação/resultado funcional.'
|
||
}
|
||
|
||
def prepare():
|
||
packet = {'files': []}
|
||
for name in ['matriz-requisitos.csv', 'backlog-priorizado.csv']:
|
||
p = DOC / name
|
||
shutil.copy2(p, PACKET / (name + '.before'))
|
||
rows = list(csv.reader(p.open(encoding='utf-8-sig', newline='')))
|
||
headers = rows[0]
|
||
patches = []
|
||
allowed = set()
|
||
for i, row in enumerate(rows[1:], 1):
|
||
identity = row[headers.index('ID')]
|
||
if identity not in notes:
|
||
continue
|
||
values = {}
|
||
if name.startswith('matriz'):
|
||
for field in ['Implementação encontrada', 'Evidência funcional']:
|
||
values[field] = row[headers.index(field)] + '\n' + tag + notes[identity]
|
||
values.update({'Disponibilidade ativa': 'READONLY '+m['backup']['at']+':investemoz:5432;050000/2colunas ausentes;flagfalse;0organizações/0memberships;2gestores elegíveis comMFA. Código/build preparados, sem ativação.', 'Lacuna': tag+gaps[identity], 'Dependência': dependencies[identity], 'Critério de aceitação': acceptance[identity], 'Versões divergentes': row[headers.index('Versões divergentes')]+'\n'+tag+'Anterior84a0e4a33dbf/21fontes/produtopendente preservado; atual36f7722ce5a5/22fontes/produção pronta. 288ativo≠295QA; não são contagens do mesmo alvo.'})
|
||
field = 'Critérios e versões por origem (JSON)'
|
||
history = json.loads(row[headers.index(field)])
|
||
entry = {'at': stamp, 'code_hash': code, 'delivery_revision': revision, 'build_sha256': build, 'evidence': notes[identity], 'remaining': gaps[identity], 'prior_current_claims': {h: row[headers.index(h)] for h in values}, 'active_writes': 0}
|
||
if isinstance(history, dict):
|
||
history['candidate_production_20261011'] = entry
|
||
else:
|
||
assert isinstance(history, list)
|
||
history.append({'candidate_production_20261011': entry})
|
||
values[field] = json.dumps(history, ensure_ascii=False, separators=(',', ':'))
|
||
else:
|
||
values = {'Pendência': gaps[identity], 'Correção/componentes': notes[identity], 'Dependência/pergunta': dependencies[identity], 'Aceitação': acceptance[identity], 'Ordem do próximo lote': 'COUNTRY02: revisão/autorização separada; execução só após autorização. Restantes frentesQA mantêm prioridades anteriores.'}
|
||
allowed.update(values)
|
||
patches.append({'row': i, 'values': values})
|
||
assert len(patches) == 3
|
||
packet['files'].append({'filename': name, 'rows': rows, 'input_sha256': sha(p), 'allowed_columns': list(allowed), 'patches': patches})
|
||
write(PACKET / 'csv-edits.json', packet)
|
||
print(json.dumps({'packet': PACKET.name, 'requirements': 3, 'state_changes': 0}))
|
||
|
||
def verify():
|
||
result = {}
|
||
packet = read(PACKET / 'csv-edits.json')
|
||
for edit in packet['files']:
|
||
current = list(csv.reader((DOC / edit['filename']).open(encoding='utf-8-sig', newline='')))
|
||
assert len(current) == len(edit['rows'])
|
||
allowed = {(p['row'], current[0].index(k)): v for p in edit['patches'] for k, v in p['values'].items()}
|
||
for i, row in enumerate(current):
|
||
for c, v in enumerate(row):
|
||
assert v == allowed.get((i, c), edit['rows'][i][c])
|
||
result[edit['filename']] = {'rows': len(current)-1, 'changed_requirements': 3, 'unrelated_cells_preserved': True, 'sha256': sha(DOC / edit['filename'])}
|
||
rows = list(csv.DictReader((DOC / 'matriz-requisitos.csv').open(encoding='utf-8-sig', newline='')))
|
||
counts = dict(collections.Counter(r['Estado'] for r in rows))
|
||
assert len({r['ID'] for r in rows}) == len(rows) == 3393
|
||
result.update({'requirements': len(rows), 'source_rows': sum(int(r['Quantidade de linhas de origem']) for r in rows), 'states': counts, 'active_writes': 0})
|
||
assert result['source_rows'] == 6845
|
||
write(PACKET / 'preservation.json', result)
|
||
print(json.dumps(result, ensure_ascii=True))
|
||
|
||
def documents():
|
||
b = m['backup']
|
||
intro = f'''**Pacote COUNTRY02 pronto para revisão, sem ativação autorizada.** Código `{code}` substitui `84a0e4a33dbf`; entrega `{revision}`; produção `{build}`. A/B e C37 QA18 mantêm os fechos aceites, sem novos ensaios dessas contas/cenários. Zero escritas na base ativa. Este é o retrato vigente destes critérios; as secções datadas seguintes conservam os estados anteriores como histórico.
|
||
|
||
A alteração necessária foi C109: o listener Fortify só registava MFA na sessão quando040000 estava disponível, enquanto o instalador exigia esse registo. Agora só um challenge válido regista a prova recente, independentemente do módulo opcional. Um código inválido mantém a sessão não autenticada; logout elimina a prova. `AppServiceProvider.php` passou a integrar o fingerprint, de21para22ficheiros; não se alteraram papéis nem representações.
|
||
|
||
Compilação de produção em cópia isolada com configuraçãoVite original, rotasWayfinder geradas naQAguardada, URLs `/build/` e título público `VITE_APP_NAME=InvestMoz`; `APP_NAME=Laravel` e `.env` ativos preservados.157ativos fixados;39carregados noChromium conferidos byte/hash, sem errosJS; país salvo/recuperado em nova sessão e390px sem extravasamento. Ensaio final usa sessões`database`, como o ativo; cookies/alvo/cache/runtimeQA ficam isolados. Plataforma ativa inspecionada semHTTP: ApachecarregaPHP8.5.11 ePDOpgSQL/mbstring/openssl/zip, compatível com o lock mínimo8.4.1. Não se aplicou configuração nem se reiniciou o servidor ativo.
|
||
|
||
Backup `{b['at']}`: origem `investemoz:5432` READONLY; restauro `{b['restored_target']['database']}:55441`; **288tabelas,31sequências,154ficheiros/ativos,0ausentes,2envelopesMFAoriginais recuperados**. Comparação de tabelas/valores e sequências a frio; hashes/extracção dos arquivos; chave efetiva preservada. Bundle e revisão ligados à prova. Clone do ensaio visual e clone final têm o mesmo snapshot de origem; não se confundem fixtures posteriores com dados ativos. Os157ativos do novo bundle são outra contagem, não os154itens do backup.
|
||
|
||
QA295 tem exatamente sete tabelas adicionais:030000 cria`opportunity_documents`,`opportunity_images`,`opportunity_location_drafts`;040000 cria`portal_profile_requests`,`portal_role_assignments`,`portal_security_approvals`,`portal_superuser_pair`. Nenhuma tabela doativo em falta.190000/050000 acrescentam colunas, não explicam essas sete tabelas. As295tabelas/103ficheiros históricos eramQA com fixtures; o backup atual inclui originais+runtime+bundle anterior, com âmbito diferente.
|
||
|
||
Ativo:0organizações/0memberships;2gestores existentes verificados comMFA configurado, nenhum escolhido. **Não é preciso inventar dados reais para instalar ou habilitar a extensão.** Reutilização do mestre requer uma organização legítima comUUID/nome/tipo/origem e membership autorizado para o utilizador; roleadmin/manager/officer pode editar,viewer consultar. PaísISO e referência podem ser preenchidos depois peloPUTautorizado; a cópia para empresa é explícita e preserva versões anteriores. Empresa privada declarada não cria organização/vínculo. Não foi encontradoCRUDgeral de criação de organizações/atribuição de representação: esse onboarding é uma frente posterior autorizada e não uma funcionalidade afirmada como existente. Sem dados legítimos, a interface apresenta o estado vazio controlado já ensaiado.
|
||
|
||
Provas locais e pacote de revisão: [manifesto](../../../../outputs/investmoz-country02-candidate-20261011/manifesto-candidata.json), [guia e critérios](../../../../outputs/investmoz-country02-candidate-20261011/REVER-CANDIDATA.md), [matriz existente](matriz-requisitos.csv). Estes caminhos são relativos ao repositório; os recibos sanitizados estão também no pacote de revisão.
|
||
'''
|
||
action = f'''### Ação autenticada e efeitos — somente após autorização
|
||
|
||
1. Rever e autorizar a entrega `{revision}`. Revalidar código22fontes, produção157ativos, alvo`investemoz:5432`, schema/flags/runtime e prova de backup com idade máxima24h. Se mudar qualquer componente/alvo ou houver divergência, renovar a prova antes de aplicar; não substituir readiness por um testeunitário.
|
||
2. Gestor existente entra com email verificado eMFA concluído nos últimos15min. URL configurado do ativo: **`http://portal-investidor.test/management/opportunity-resources-install/organization-country`**. Não foi feito loginHTTPativo nesta preparação. GETapresenta prévia/planohash/dependências e contagens;installedfalse,consistenttrue,readytrue é preparação, não instalação.
|
||
3. FormulárioPOSTna mesma rota, protegido por sessão/CSRF: `hash` da prévia atual, `confirm=true`, novo`idempotency_key`UUID, `reason`20–1000caracteres (revisão/motivo). Aplicar **apenas**`2026_10_10_050000_extend_organization_country`seausente, via mecanismo autenticado existente. Não usar migrateglobal. Esperado:installedtrue,changedtrue,new_columns2,new_tables0,new_organizations0,flags_changedfalse;1registro de migration e auditoria da operação. Repetir o mesmoUUID devolve o recibo anterior semDDL/auditoria duplicados;novoUUID após instalação é consulta idempotente comchangedfalse e auditoria própria.
|
||
4. Disponibilizar apenas`PORTAL_ORGANIZATION_COUNTRY_ENABLED=true`; preservar todas as outras flagsfalse, chave/cipher, consentimentos, classificações, URLs e dados. A configuração ativa não está em cache; não gerar cache global por arrasto. Atualizar somente o runtime alvo e confirmar a flag efetiva.
|
||
5. Verificar e acrescentar os novos ativos imutáveis em`public/build`sem remover ativos anteriores. Colisão de nome exige hash igual; nunca sobrescrever bytes diferentes. Preparar`manifest.json.new`e conferirSHA`{build}`; substituir`manifest.json`por operação atómica com cópia do anterior. NoWindowsusar`System.IO.File.Replace`em ficheiros no mesmo volume, com caminhos previamente verificados dentro do workspace. Não extrair o pacote inteiro sobre alterações locais. Manter ativos antigos para páginas abertas e reversão.
|
||
6. Confirmar manifesto/código/schema/flag e perfil mestre disponível; a conta sem representação deve manter estado vazio controlado. Nenhuma conta/org/vínculoQA entra no ativo;0alterações de privilégios. Conferir preservação dos valores anteriores/auditoria. Dados reais só pelo fluxo/ação posteriormente autorizados. O fechoQA não é uma demonstração doativo.
|
||
|
||
**Sucesso objetivo:** hashes da candidata correspondentes,050000ledger+2colunascoerentes,0tabelas/contas/org/memberships novos por instalação, auditreceipt/UUIDcorretos, flagcountrytrue e outrasflagsinalteradas, assets200 com hashes fixados, interface do perfil sem500/422 inesperado, vazio controlado quando não há representação; histórico/submissões originais preservados. Aprovação não implica divulgação, cálculo,CRMexterno ou decisão externa.
|
||
|
||
**Reversão:** em caso de erro de compatibilidade, autorização indevida, perda/mistura de dados, manifesto divergente ou falha do consumidor, pôr somente a flagcountryfalse e restaurar atomicamente o manifesto anteriorSHA`{m['active_manifest_sha256']}`; manter ativos antigos/novos,2colunas,ledger,valores/proveniência/histórico,auditoria e chave. Não fazer down/drop/reset/key:generate nem retirar privilégios. Recuperação de dados, se realmente necessária, usa o restauro isolado verificado e autorização específica; não sobrescrever alterações legítimas posteriores ao snapshot.
|
||
|
||
**Fora da candidata:**030000→040000→2026_10_09_190000 continua a sequência das outras frentes, sem repetição nem ativação por arrasto. Ofertas, competências funcionais modulares eSLA/calendário/nomeações continuam decisões separadas;workflow/Response integral/cartografia/derivadas/acessibilidade integral/dispositivos reais mantêm os critérios restantes do backlog. MercadoFinanceiro continua privado.
|
||
'''
|
||
# The reviewed UI bundle must be served before opening the new installer form.
|
||
start = action.index('1. Rever')
|
||
end = action.index('\n**Sucesso objetivo:')
|
||
steps = re.split(r'(?m)^[1-6]\. ', action[start:end])[1:]
|
||
assert len(steps) == 6
|
||
ordered = [steps[i] for i in [0, 4, 1, 2, 3, 5]]
|
||
ordered[1] = 'Com a flag COUNTRY02 ainda desabilitada, '+ordered[1][0].lower()+ordered[1][1:]
|
||
action = action[:start]+''.join(str(i)+'. '+s for i,s in enumerate(ordered,1))+action[end:]
|
||
snippets = {
|
||
'relatorio-executivo.md': '## Candidata de produção concluída — '+stamp+'\n\n'+intro+'\nO pacote está pronto para revisão/autorização separada; funcionalidade ainda não instalada/disponibilizada no ativo. A auditoria global continua aberta.\n',
|
||
'backlog.md': '## Critérios restantes após preparação COUNTRY02 — '+stamp+'\n\n'+intro+'''\n| Requisito | Demonstrado agora | Critério restante |
|
||
| --- | --- | --- |
|
||
| COUNTRY02 | Produção/políticaMFA/instalação/recuperação/idempotência/reversãoQA | Autorizar/aplicar só050000+flag+bundle no ativo. Mestre vazio é controlado; organização/vínculo reais são necessários somente à reutilização efetiva. País/referência depois viaPUTautorizado. |
|
||
| OPS01 | Pacote delimitado, plataforma/configuração, backup/restauro/hash e288vs295 | Executar a sequência autenticada após autorização e revalidação<=24h; outras instalações/flags dependem de provas/decisões próprias. |
|
||
| DOC01 | Revisões anteriores preservadas e novo retrato ligado à mesma matriz | Restantes alegações históricas sem prova individual continuam abertas. |
|
||
\nDemais pendências e prioridades abaixo mantêm-se; não repetir A/B/C37. Próximo trabalhoQA executável continua Response/§27, materialidade por categorias/recursos, países/origem/filtros/estatísticas por consumidor, mapas/documentos/acessibilidade pelos critérios já enumerados. Não aprovar ofertas/competências/SLA por integração do legado.\n''',
|
||
'plano-ativacao.md': '## Plano final delimitado COUNTRY02 — '+stamp+'\n\n'+intro+'\n'+action,
|
||
'casos-reproducao.md': f'''## C109 — MFA do instalador sem040000 — {stamp}
|
||
|
||
**Requisito:**COUNTRY02/OPS01. **Causa:**AppServiceProvider condicionava a escrita de`portal.mfa_actor`/`portal.mfa_at`ao módulo de perfis disponível;PortalProfileInstallation exigeMFArecente mesmo no ramo manage-portal anterior a040000. PreserveFormFeedback não é a causa;C37QA18 permanece encerrado.
|
||
|
||
**Reproduzir no clone:** fonte288tabelas,030000/040000/190000ausentes, emailverificado/gestor sintético comMFA normalmente configurado; login→challengeválido→préviaCOUNTRY02→POSTconfirmado comCSRF/hash/UUID/reason. **Esperado:**2colunas opcionais/ledger/auditoria. **Antes:**403"Conclua MFA nesta sessão"apesar dochallengeválido. **Correção:**registo da prova unicamente no evento Fortify de código válido, sem depender de040000; fingerprint incorpora oprovider.
|
||
|
||
**Provas:**CountryCandidateMfaTest confirma código inválido/guest, código válido/autenticado/prova e logout/semprova;regressão6testes90assertions;Chromiumdo bundleprodução confirmaMFAsem040000, gravação/recuperação em nova sessão comdriverdatabase e idempotência. Primeira instalação visual a frio emcountry_candidate_20261011_060236_test;restauro finalcountry_candidate_20261011_060946_test com snapshot de origem idêntico. Recibos sanitizados omitem códigos/segredos/cookies e corpos de autenticação. A/B/C37 não foram repetidos.
|
||
|
||
**Observações do harness:**a primeira execução usouOPcache diferente doChromiumQAexistente e foi corrigida paraopcache.enable=0; essa alteração foi só no servidor temporárioQA18101. Um locator de"Instalado"foi tornado específico; uma fixture de compatibilidade alterou oMFA de um ator sintético partilhado, pelo que o ensaio passou a usar ator próprio e operações sequenciais. Tentativas anteriores foram preservadas; a tentativa final passou. Nenhum destes problemas reabreC37daQA18 ou altera oativo. ServidorQA18101 encerrado depois dos ensaios;QA18097 preservada.
|
||
'''
|
||
}
|
||
for name, content in snippets.items():
|
||
p = DOC / name
|
||
marker = 'country-production-final-20261011'
|
||
old = p.read_text(encoding='utf-8-sig')
|
||
assert '<!-- '+marker+':begin -->' not in old
|
||
p.write_text('<!-- '+marker+':begin -->\n\n'+content+'\n<!-- '+marker+':end -->\n\n'+old, encoding='utf-8')
|
||
(OUT / 'REVER-CANDIDATA.md').write_text('# COUNTRY02 — pacote para revisão\n\n'+intro.replace('(../../../../outputs/investmoz-country02-candidate-20261011/', '(').replace('(matriz-requisitos.csv)', '(../../docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv)')+'\n'+action, encoding='utf-8')
|
||
print(json.dumps({'documents_updated': list(snippets), 'approval_requested_or_applied': False}))
|
||
|
||
def package():
|
||
assert m['activation_ready'] and not m['activation_authorized']
|
||
evidence = OUT / 'evidencias'
|
||
evidence.mkdir(exist_ok=True)
|
||
for name in ['country-production-chromium-20261011.json', 'country-production-file-session-20261011.json', 'country-production-persistence-20261011.json', 'country-production-cold-persistence-20261011.json', 'country-production-profile-390-20261011.png']:
|
||
shutil.copy2(PRIVATE / name, evidence / name)
|
||
write(evidence / 'nota-capturas.json', {'scope': 'The packaged PNG belongs to the final database-session run. The earlier file-session receipt keeps its original bytes/hash; its temporary image filename was reused. Its cold MFA/install evidence is the UI/network sequence and persisted audit, not the current PNG.'})
|
||
source_files = {name: ROOT / name for name in m['files']}
|
||
asset_files = {'public/build/'+name: Path(m['production']['directory']) / name for name in m['production']['files']}
|
||
all_files = {**source_files, **asset_files, 'manifesto-candidata.json': OUT / 'manifesto-candidata.json', 'REVER-CANDIDATA.md': OUT / 'REVER-CANDIDATA.md', **{'evidencias/'+p.name: p for p in evidence.iterdir()}}
|
||
assert all('private' not in Path(n).parts and '.env' not in Path(n).parts and 'vendor' not in Path(n).parts and 'node_modules' not in Path(n).parts for n in all_files)
|
||
for name, expected in m['files'].items(): assert sha(source_files[name]) == expected
|
||
for name, expected in m['production']['files'].items(): assert sha(asset_files['public/build/'+name]) == expected
|
||
zipped = OUT / ('country02-'+revision[:12]+'-revisao.zip')
|
||
if zipped.exists():
|
||
history = OUT / 'historico-pacotes'
|
||
history.mkdir(exist_ok=True)
|
||
shutil.move(zipped, history / (zipped.stem+'-'+sha(zipped)[:12]+'.zip'))
|
||
shutil.copy2(OUT / 'manifesto-pacote.json', history / ('manifesto-pacote-'+stamp.replace(':','-')+'.json'))
|
||
with zipfile.ZipFile(zipped, 'x', zipfile.ZIP_DEFLATED) as z:
|
||
for name, p in all_files.items(): z.write(p, name)
|
||
with zipfile.ZipFile(zipped) as z:
|
||
for name, p in all_files.items(): assert hashlib.sha256(z.read(name)).hexdigest() == sha(p)
|
||
envelope = {'candidate': m['candidate_id'], 'code_hash': code, 'delivery_revision': revision, 'manifesto_candidata_sha256': sha(OUT / 'manifesto-candidata.json'), 'production_manifest_sha256': build,
|
||
'archive': zipped.name, 'archive_sha256': sha(zipped), 'archive_entries': len(all_files), 'bytes': zipped.stat().st_size, 'verified_members_sha256': {n: sha(p) for n, p in all_files.items()},
|
||
'excludes': ['.env, secrets, credentials, MFA codes, private data/documents/dumps/readiness', 'vendor/node_modules', 'real master data or representation assignments'], 'activation_authorized': False, 'active_writes': 0}
|
||
write(OUT / 'manifesto-pacote.json', envelope)
|
||
print(json.dumps({k: envelope[k] for k in ['code_hash','delivery_revision','archive','archive_sha256','archive_entries','bytes','active_writes']}))
|
||
|
||
{'prepare': prepare, 'verify': verify, 'documents': documents, 'package': package}[sys.argv[1]]()
|