Files
Investidor-V2/tools/portal-country-package-results.py
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

194 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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]]()