134 lines
11 KiB
Python
134 lines
11 KiB
Python
"""Record the owner's binary-identity confirmation without new source/requirement rows."""
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
from collections import Counter
|
|
import csv, hashlib, json, shutil, sys
|
|
|
|
csv.field_size_limit(20_000_000)
|
|
root=Path(__file__).resolve().parent.parent
|
|
private=root/'storage/app/private/investmoz-v2/portal-audit'
|
|
source_dir=private/'segmentation-reconciliation-20261011'
|
|
directory=private/'segmentation-identity-confirmation-20261011'
|
|
docs=root/'docs/investmoz/v2/auditoria-portal'
|
|
out=root/'outputs/investmoz-segmentation-reconciliation-20261011'
|
|
read=lambda p:json.loads(p.read_text(encoding='utf-8-sig'))
|
|
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
|
write=lambda p,v:p.write_text(json.dumps(v,ensure_ascii=False,indent=2),encoding='utf-8')
|
|
digest='bf26caeee0437a8d292bf29be59247e2862c14e79b5ae465c00322747af72af5'
|
|
local=Path('C:/Mac/Home/Documents/TEMPO/Portal Tempo/Servicos Tempo/Relatorios/novos/segmentar processos v1.docx')
|
|
old_tag='[Segmentação bf26caeee043 — exemplar v1 local; identidade v1(3) pendente] '
|
|
new_tag='[Segmentação bf26caeee043 — identidade confirmada; nome local v1.docx] '
|
|
counts=read(source_dir/'counts.json')
|
|
ids=set(counts['reused_requirement_IDs']+counts['new_IDs'])
|
|
assert sha(local)==digest and sha(Path(read(source_dir/'source-provenance.json')['retained']))==digest
|
|
|
|
if sys.argv[1]=='prepare':
|
|
assert not directory.exists(), 'Confirmation preparation already exists; do not duplicate.'
|
|
directory.mkdir()
|
|
before=directory/'before';before.mkdir()
|
|
for f in ['matriz-requisitos.csv','backlog-priorizado.csv','inventario-fontes.csv','backlog.md','relatorio-executivo.md','segmentacao-processos-reconciliacao-20261011.md']:
|
|
shutil.copy2(docs/f,before/f)
|
|
for f in ['source-provenance.json','correspondence.json','final-integrity.json']:
|
|
shutil.copy2(source_dir/f,before/f)
|
|
shutil.copytree(out,before/'outputs')
|
|
receipt={'at':datetime.now(timezone.utc).isoformat(),'confirmed_by':'responsável, mensagem desta sessão de 11/10/2026',
|
|
'owner_statement':'Confirmada a identidade binária com segmentar processos v1(3).docx; v1.docx é nome local do mesmo exemplar.',
|
|
'confirmation_basis':'Confirmação binária do responsável e hash calculado do nome local igual ao exemplar anteriormente incorporado. Não foi fingido acesso independente ao nome v1(3).',
|
|
'requested_name':'segmentar processos v1(3).docx','local_name':local.name,'local_path':str(local),'resolved_path':str(local.resolve()),
|
|
'sha256':digest,'bytes':local.stat().st_size,'exact_requested_exemplar_confirmed':True,
|
|
'new_documents':0,'new_requirements':0,'reimport':False,'active_writes':0,'functional_states_changed':False}
|
|
write(directory/'identity-confirmation.json',receipt)
|
|
files=[]
|
|
for filename in ['matriz-requisitos.csv','backlog-priorizado.csv','inventario-fontes.csv']:
|
|
rows=list(csv.reader((docs/filename).open(encoding='utf-8-sig',newline='')));headers=rows[0];patches=[]
|
|
for rowno,row in enumerate(rows[1:],1):
|
|
values={}
|
|
eligible=(row[headers.index('SHA256')]==digest if filename=='inventario-fontes.csv' else row[headers.index('ID')] in ids)
|
|
if not eligible:continue
|
|
for col,v in enumerate(row):
|
|
revised=v.replace(old_tag,new_tag)
|
|
revised=revised.replace('Confirmar exemplar solicitado v1(3). ','')
|
|
revised=revised.replace('Identidade v1(3) pendente.','Identidade binária com v1(3).docx confirmada pelo responsável em 11/10/2026; v1.docx é o nome local do mesmo exemplar.')
|
|
revised=revised.replace('; v1(3) pendente.','; mesmo exemplar de v1(3).docx, confirmado pelo responsável em 11/10/2026.')
|
|
if headers[col]=='Critérios e versões por origem (JSON)':
|
|
entries=json.loads(v)
|
|
for item in entries:
|
|
if item.get('source_sha256')==digest:
|
|
item['exact_requested_exemplar_confirmed']=True
|
|
revised=json.dumps(entries,ensure_ascii=False)
|
|
if revised!=v:values[headers[col]]=revised
|
|
if values:patches.append({'row':rowno,'values':values})
|
|
assert len(patches)==(1 if filename=='inventario-fontes.csv' else 47)
|
|
files.append({'filename':filename,'input_sha256':sha(docs/filename),'rows':rows,'patches':patches,
|
|
'allowed_columns':sorted({k for patch in patches for k in patch['values']}),'source_hash_updates':[]})
|
|
write(directory/'csv-edits.json',{'files':files})
|
|
print(json.dumps({'prepared':True,'matrix_rows_affected':47,'new_rows':0,'sha256':digest}))
|
|
elif sys.argv[1]=='finalize':
|
|
receipt=read(directory/'identity-confirmation.json');packet=read(directory/'csv-edits.json')
|
|
for edit in packet['files']:
|
|
rows=list(csv.reader((docs/edit['filename']).open(encoding='utf-8-sig',newline='')))
|
|
assert len(rows)==len(edit['rows']) and rows[0]==edit['rows'][0]
|
|
expected=[list(r) for r in edit['rows']]
|
|
for patch in edit['patches']:
|
|
for k,v in patch['values'].items():expected[patch['row']][rows[0].index(k)]=v
|
|
assert expected==rows, 'Only the requested confirmation cells may change.'
|
|
provenance=read(source_dir/'source-provenance.json')
|
|
provenance['exact_requested_exemplar_confirmed']=True
|
|
provenance['local_filename_is_alias_of_requested_exemplar']=True
|
|
provenance['identity_confirmation']=receipt
|
|
write(source_dir/'source-provenance.json',provenance)
|
|
correspondence=read(source_dir/'correspondence.json')
|
|
for item in correspondence:item['requested_exemplar_confirmation_pending']=False
|
|
write(source_dir/'correspondence.json',correspondence);write(out/'correspondence.json',correspondence)
|
|
inventory=read(private/'inventario-fontes.json')
|
|
# Preserve the old inventory episode and add the current source confirmation explicitly.
|
|
inventory.setdefault('source_identity_confirmations',[])
|
|
assert not any(x.get('sha256')==digest for x in inventory['source_identity_confirmations'])
|
|
inventory['source_identity_confirmations'].append(receipt)
|
|
write(private/'inventario-fontes.json',inventory)
|
|
summary='O responsável confirmou em 11/10/2026 a identidade binária entre `segmentar processos v1(3).docx` e o nome local `segmentar processos v1.docx`. O hash local coincide com o original já incorporado. Não houve nova importação, documento, requisito ou prova funcional por causa do nome. A ressalva anterior foi retirada; a confirmação e o retrato anterior ficam conservados no histórico. As pendências funcionais e a precedência das definições aprovadas permanecem.'
|
|
for filename in ['backlog.md','relatorio-executivo.md']:
|
|
p=docs/filename;t=p.read_text(encoding='utf-8')
|
|
a=t.index('<!-- segmentation-reconciliation-20261011:begin -->');b=t.index('<!-- segmentation-reconciliation-20261011:end -->',a)
|
|
part=t[a:b]
|
|
part=part.replace('## Segmentação de processos incorporada em QA com confirmação de exemplar pendente','## Segmentação de processos incorporada em QA com identidade confirmada')
|
|
old='O pedido identifica`v1(3).docx`, ainda não localizado/confirmado; não declarar os exemplares equivalentes.'
|
|
assert old in part;part=part.replace(old,summary)
|
|
p.write_text(t[:a]+part+t[b:],encoding='utf-8')
|
|
p=docs/'segmentacao-processos-reconciliacao-20261011.md';t=p.read_text(encoding='utf-8')
|
|
original='O exemplar acessível **segmentar processos v1.docx** foi conservado e importado privadamente na biblioteca QA existente. **Ainda não foi localizado nem confirmado o exemplar solicitado segmentar processos v1(3).docx.** Não se declara identidade entre esses ficheiros; a pergunta sobre o caminho exato permanece pendente. O sufixo de nome (3) não é tratado como versão funcional 3.\n\nEsta reconciliação usa o original disponível e os pedidos expressos desta sessão, preservando o histórico. A confirmação do exemplar solicitado é o critério documental restante; se os bytes forem diferentes, comparar as secções antes de alterar os vínculos. Não repetir provas funcionais por uma simples cópia de nome diferente.'
|
|
assert original in t
|
|
t=t.replace(original,'O exemplar **segmentar processos v1.docx** foi conservado e importado privadamente na biblioteca QA existente. '+summary+' O sufixo de nome (3) não é uma versão funcional 3.')
|
|
p.write_text(t,encoding='utf-8');(out/'relatorio.md').write_text(t,encoding='utf-8')
|
|
write(out/'confirmacao-identidade.json',receipt)
|
|
proof=read(source_dir/'final-integrity.json')
|
|
proof['at']=datetime.now(timezone.utc).isoformat();proof['actual_requested_exemplar_pending']=False
|
|
proof['exact_requested_exemplar_confirmed']=True;proof['identity_confirmation']=receipt
|
|
qa=read(directory/'source-identity-qa.json')
|
|
assert qa['result']=='passed' and qa['metadata_version']==3 and qa['historical_revisions']==3
|
|
assert qa['original_import_audit_count']==1 and qa['identity_audit_count']==1
|
|
proof['metadata_version']=3
|
|
proof['QA_evidence']['source-identity-qa.json']=sha(directory/'source-identity-qa.json')
|
|
proof['files']={f:sha(docs/f) for f in proof['files']}
|
|
proof['QA_evidence']['identity-confirmation.json']=sha(directory/'identity-confirmation.json')
|
|
matrix=list(csv.DictReader((docs/'matriz-requisitos.csv').open(encoding='utf-8-sig',newline='')))
|
|
assert len(matrix)==3401 and sum(int(r['Quantidade de linhas de origem']) for r in matrix)==7739
|
|
assert Counter(r['Estado'] for r in matrix)==Counter(counts['states'])
|
|
for name,h in read(private/'functional-active-files-before-20261011.json').items():assert sha(root/name)==h
|
|
candidate=root/'outputs/investmoz-opportunity-workflow-candidate-20261011'
|
|
assert sha(candidate/'investmoz-workflow-3d9d392e0a0f.zip')==proof['sealed_workflow_candidate']['zip_sha256']
|
|
assert sha(candidate/'manifesto.json')==proof['sealed_workflow_candidate']['manifest_sha256']
|
|
proof['result']='passed'
|
|
write(source_dir/'final-integrity.json',proof);write(out/'verificacao.json',proof)
|
|
manifest=read(out/'manifesto.json')
|
|
manifest['scope']='Confirmed v1(3) exemplar, local filename v1.docx; no active activation'
|
|
manifest['identity_confirmed_by_owner']=True
|
|
manifest['previous_manifest_sha256']=sha(directory/'before/outputs/manifesto.json')
|
|
manifest['files']={p.name:sha(p) for p in out.iterdir() if p.is_file() and p.name!='manifesto.json'}
|
|
write(out/'manifesto.json',manifest)
|
|
assert all(sha(out/name)==h for name,h in manifest['files'].items())
|
|
write(directory/'verification.json',{'result':'passed','requirements':3401,'source_occurrences':7739,'source_inventory_entries':604,
|
|
'states_unchanged':True,'new_documents':0,'new_requirements':0,'reimport':False,'active_writes':0,'sealed_workflow_preserved':True})
|
|
print(json.dumps({'result':'passed','confirmation_recorded':True,'new_requirements':0,'reimport':False,'active_writes':0}))
|
|
else:raise ValueError('Use prepare or finalize')
|