11 · Tabelas de propriedades¶
Uma pergunta de cada vez é o método feito à mão. Uma política com doze resultados indesejados são doze perguntas, e três coisas dão errado quando elas são feitas à mão: uma edição da política quebra em silêncio uma obrigação provada, porque nada a pergunta de novo; as perguntas e a política se afastam; e uma pergunta que ninguém anotou fica exatamente igual a uma que vale. Uma tabela de propriedades é a lista de perguntas como um arquivo, versionado ao lado da política e rodado a cada mudança. Este capítulo mostra como escrever uma, como rodá-la e como ler o que ela devolve.
A tabela¶
Uma tabela é JSON: a política de que ela trata, e uma linha por obrigação.
{
"policy": "../verification/transfers.co",
"properties": [
{
"id": "TR-01",
"description": "A transfer runs for a user who is not signed in.",
"action": "execute_transfer_funds",
"context": "is_authenticated",
"pins": {},
"expect": "UNSAT",
"gate": "hard",
"certificate": "required"
},
{
"id": "TR-02",
"description": "The refusal stays reachable for a user who is not signed in.",
"action": "refuse unauthorized action",
"context": "is_authenticated",
"pins": {},
"expect": "SAT",
"gate": "hard",
"seatbelt": false,
"seatbelt_waiver": "Liveness, not safety: this row wants its SAT. A repair would make the refusal unreachable, breaking the behaviour TR-01 relies on."
}
]
}
| Campo | O que diz |
|---|---|
policy |
o arquivo de política, resolvido em relação à tabela, para que os dois se movam juntos |
id |
a identidade da linha, versionada e nunca reutilizada: aquilo a que um registro, um arquivo de auditoria ou um documento regulatório se refere |
description |
o resultado indesejado nas palavras de um revisor; o campo que um leitor de compliance lê |
action |
a ação que não pode disparar |
context |
a obrigação sem a qual ela não pode disparar; pode nomear um rail, como name@input, name@output ou name@dialog (capítulo 4) |
pins |
os pins de condicionamento: um único objeto, nunca uma escolha entre vários. Duas condições são duas linhas |
expect |
UNSAT, SAT ou STALL: o que a obrigação deveria devolver |
gate |
hard reprova o portão da execução em qualquer coisa diferente de expect; soft registra e levanta um sinal de revisão |
certificate |
"required": a linha só é cumprida por uma refutação que um terceiro pode conferir, não só por um veredito |
pin_pair |
o radical que liga duas formas de perguntar uma obrigação, com e sem os seus pins (abaixo) |
seatbelt, seatbelt_waiver |
false dispensa o reparo que a linha deveria de outra forma, e diz por quê (abaixo) |
max_sds |
um limite para a pontuação de disrupção do reparo, numa linha que espera um |
depth |
o número de passos de conversa a desdobrar, para uma propriedade sobre uma sequência |
supersedes, retired_by |
onde uma obrigação foi dividida ou fundida: os ids que uma linha substitui, ou que a substituíram. Uma linha aposentada não roda |
A tabela é lida com rigor. Um expect digitado errado, um pin igual a 2, duas linhas com o mesmo id,
max_sds numa linha que não espera reparo: cada um rodaria e relataria alguma coisa, e essa coisa não
seria a pergunta que alguém escreveu. Cada um é recusado pelo nome, e o job falha porque a execução, e não
uma linha, falhou.
Escreva expect como a obrigação deveria responder, não como ela responde hoje. Uma linha escrita
para bater com o comportamento atual não pode falhar, e uma tabela de linhas que não podem falhar é o
modo de falha que esta ferramenta mais provavelmente produz. O capítulo 4 mostra
como obter linhas candidatas da própria política, e o capítulo 12, como conferir os pins.
Rodando¶
result = client.verify_batch("properties.json", profile="patch")
for record in result.records:
print(record["id"], record.get("verdict"), record.get("result"), record.get("discharge"))
O perfil padrão é fast. Toda linha é decidida pelo solver SAT, e uma linha é resolvida uma segunda vez,
pela expansão algébrica, só onde a sua resposta deve o que o solver SAT não consegue construir: um SAT
ou STALL cujo reparo o portão de patch confere, e um UNSAT numa linha que exige certificado. Uma
tabela verde de linhas sem condições paga pelo solver SAT e por nada mais. O proof faz o mesmo com o
certificado completo na primeira resolução, e o patch decide e repara toda linha numa única resolução
algébrica.
Um veredito, ou um veredito que outra pessoa pode conferir: certificate: required¶
Um veredito que um terceiro não pode conferir é uma afirmação. Para uma operação registrada ou um
pagamento liberado, a diferença entre uma afirmação e uma prova é todo o argumento para compilar a
política. Por isso, uma linha que diz "certificate": "required" só é cumprida por uma refutação que
pode ser conferida, e uma tabela com uma linha dessas é recusada antes de qualquer resolução sob uma
configuração que não consegue produzi-la: sob fast, que não constrói certificado, e sob proof, cujas
refutações do solver SAT são afirmações do oráculo. A recusa nomeia as linhas. Use patch.
A única dispensa é explícita: allow_oracle_refutations, que aceita que essas linhas se apoiem numa
afirmação, e registra que se apoiam.
As duas formas de perguntar uma obrigação condicionada: pin_pair¶
O motor também pergunta cada linha sem os seus pins. Uma linha que vale das duas formas tem pins que a
enfeitavam em vez de sustentá-la. Às vezes isso está certo, e os pins têm de ficar: eles declaram a
própria condição da regulação, e um revisor lê a linha contra a regulação, não contra a política.
Versione as duas formas como duas linhas que compartilham um radical pin_pair. Elas não podem diferir em
nada além dos pins, e no máximo uma delas pode ser hard. O que você ganha é uma diferença visível: uma
mudança na política que separa as duas afirmações aparece como uma linha que se moveu.
Dispensando o reparo numa linha¶
Uma linha que não prova que o seu resultado é impossível deve um reparo, e SAT e STALL o devem
igualmente: não conseguir descartar um resultado exige a mesma resposta que provar que ele é alcançável.
Algumas linhas fazem uma pergunta diferente. A recusa ainda tem de ser alcançável para um usuário que
não fez login é uma afirmação de vivacidade: ela quer o seu SAT, e um reparo tornaria a recusa
inalcançável. Uma linha dessas define "seatbelt": false, com uma frase em seatbelt_waiver dizendo por
quê.
- A frase é obrigatória. Desligar o reparo é a única edição que deixa verde uma linha vermelha sem tocar na política, então ela tem de deixar um motivo registrado.
- Ela dispensa a busca, não só a conferência: uma linha dispensada pula a busca de reparo inteira.
- Ela vai para o registro da linha, ao lado do motivo, para não poder ser apagada da evidência depois.
- Ela é recusada numa linha que espera
UNSAT, onde não há reparo a dispensar.
Lendo os registros¶
O job devolve um registro por linha, e um resumo.
{
"id": "TR-01",
"action": "execute_transfer_funds",
"context": "is_authenticated",
"expect": "UNSAT",
"verdict": "UNSAT",
"profile": "patch",
"gate": "hard",
"result": "pass",
"replay": "replayed",
"origin_class": "derived",
"self_contained": true,
"conformance": "pass",
"discharge": "replayed",
"seconds": 0.002,
"void": false
}
profile está em todo registro e no resumo, porque decide o que o registro pode afirmar. Quatro campos
descrevem a evidência, e não o veredito, e são quatro de propósito:
| Campo | O que diz |
|---|---|
origin_class |
derived, exhaustion, external_oracle ou external_proof: que tipo de refutação é esta, o que decide como ela pode ser conferida (capítulo 7) |
self_contained |
se o certificado sozinho resolve a questão |
conformance |
se o certificado cumpre as regras do formato (capítulo 7) |
discharge |
numa linha certificate: required: replayed, refutation-checked, external-oracle ou failed |
Uma linha aposentada também aparece, como "status": "retired" com os ids que a substituíram, para que
os registros prestem contas de todo id que a tabela traz.
Uma linha obrigatória que falha está nos registros, não no status HTTP¶
O estado do job é sobre a execução, não sobre as linhas: uma tabela cujas linhas falham é um job que teve
succeeded, porque a execução funcionou. Decida um build pelo resumo:
hard_failureseerrors: uma linha obrigatória que não respondeu como esperado, ou que não pôde ser respondida. Uma linha obrigatória não respondida está nos dois, porque uma pergunta obrigatória que ninguém respondeu não passou;void: a política não compilou por inteiro, e todo veredito da execução é nulo (capítulo 6);structural_no_ops: linhas cujo contexto ou pins nomeiam uma variável que a política não tem. Uma linha dessas não perguntou nada, então falha qualquer que seja o seu veredito: um contexto escrito errado passaria de outra forma como um desvio provado, ou deixaria verde uma linha que esperaSAT;conformance_failureseundischarged: um certificado que quebrou o formato, ou uma linhacertificate: requiredque não foi cumprida por uma refutação conferível.
No cliente, result.hard_failures e result.all_passed leem o resumo; if result: é recusado, porque
funcionou? significa quatro coisas aqui.
summary = result.summary
if summary.get("void") or summary.get("structural_no_ops") or summary.get("conformance_failures") \
or summary.get("undischarged") or result.hard_failures or summary.get("errors"):
raise SystemExit(f"the property table failed: {result.hard_failures} hard failure(s)")
print("every hard row answered as expected")
Uma linha ruim não detém as outras: uma linha que nomeia uma ação que a política não registra recebe um
registro error, e a tabela segue.
Linhas não lidas custam as linhas que alcançam¶
Uma linha cuja pergunta pode alcançar uma linha que o compilador não conseguiu ler fica
"status": "undecided" com "reason": "colang_unread_in_cone", nomeando a linha, e falha. Uma linha que
a linha não lida não alcança é respondida. undecided_rows e voided_rows contam o que as linhas não
lidas custaram. Parece estranho que uma linha ilegível registre uma falha para cada linha que alcança, e a
alternativa é pior: um build que decidisse só por hard_failures ficaria verde numa execução em que nada
foi decidido, o que faria de uma política que não compila o caminho mais barato para uma tabela limpa.
O portão de patch¶
Uma linha que espera SAT ou STALL é um defeito conhecido com um reparo, e o reparo é conferido, não
aceito de confiança. O portão nunca compara o texto do reparo, porque o texto varia entre execuções
(capítulo 8). Ele confere cada propriedade que um reparo precisa ter:
| Conferência | Falha quando |
|---|---|
patch_produced |
nenhum reparo foi devolvido |
certificate_replays |
o certificado do reparo não se reproduz até 0 = 1 |
final_step_withheld |
os passos listados já chegam a 0 = 1, sem deixar nada para o verificador fazer |
self_contained |
o certificado não se apoia só na política |
no_oracle_dependencies |
um procedimento externo foi afirmado dentro da derivação |
sds_within_bound |
a pontuação de disrupção excede o max_sds da linha |
search_not_truncated |
a busca atingiu o seu orçamento, então o menos disruptivo foi retirado |
author_visible_variables |
o reparo nomeia uma variável interna ou uma intenção, sobre as quais nenhum mantenedor pode agir |
mentions_anchor_action |
o reparo restringe a política sem nomear a ação que protege |
not_over_blocking |
o bloqueio excessivo não foi descartado, numa linha que nomeou pins |
Uma falha nomeia a conferência e o que ela encontrou, em patch_gate_detail. Uma tabela com uma linha
que deve um reparo é recusada de saída sob uma config que desliga a busca de reparo, ou que não escreve
certificado: de outra forma, o portão relataria a ausência de um reparo que ninguém pediu à execução que
encontrasse.
Os limites¶
- Uma tabela verde prova as linhas que contém. Não prova nada sobre uma obrigação que ninguém anotou, nem sobre uma que a política nunca menciona (capítulo 4).
- Um veredito sobre uma política com diagnósticos de análise ao alcance de uma linha não é um veredito: essa linha fica indecisa e falha, e todas as linhas de uma execução nula também.
- Uma linha escrita para bater com o comportamento de hoje não prova nada;
expecté uma declaração do que deve valer. - O status do job diz se a execução funcionou. Se a tabela passou está no seu resumo.