Whitepaper · 11
Guia de Implementação
Do clone limpo a um token de lote implantado, com hooks e atestações, passo a passo. Todo bloco de JavaScript deste capítulo é copiado de test/standard/ImplementationGuide.test.js e executado na CI; se um bloco daqui estivesse errado, o build falharia.
Como usar este guia
As etapas se apoiam umas nas outras e compartilham variáveis: token, attestations, os signers e os ids criados ao longo do caminho. Leia-as em ordem na primeira vez. Execute-as por conta própria com:
npx hardhat test test/standard/ImplementationGuide.test.js
| Etapa | O que se obtém | Quem age | Chamadas principais |
|---|---|---|---|
| 1 · Configuração | uma cópia de trabalho compilada e testada | desenvolvedor | npm ci, npm test |
| 2 · Implantação | proxy do token + registro de atestações | implantador | upgrades.deployProxy, ARexAttestations |
| 3 · Papéis | operadores com privilégio mínimo | administrador | grantRole |
| 4 · Criação | um lote com compromisso de metadados | emissor | createBatch |
| 5 · Leitura | o registro do lote e a oferta | qualquer pessoa | batchOf, totalSupply(uint256), parentsOf |
| 6 · Transferência | uma transferência verificada | titular | transferCheck, safeTransferFrom |
| 7 · Linhagem | divisão, união, processamento | titular / transformador | transform |
| 8 · Status | atualizações de ciclo de vida e de metadados | papel de status | setBatchStatus, setBatchMetadata |
| 9 · Hooks | um pipeline ordenado de regras | administrador de hooks | registerHook, getHooks |
| 10 · Atestação | evidência que libera transferências | emissor confiável | setTrustedIssuer, attest |
| 11 · Sanções | elegibilidade com fonte e TTL | gestor de elegibilidade | setSanctioned |
| 12 · Enforcement | congelamento, pausa, transferência forçada | responsável pelo enforcement / responsável pela pausa | setFrozenTokens, forcedTransfer |
| 13 · Baixa | fim de vida de um lote | papel de status, titular | setBatchStatus(…, 255, …), burn |
| 14 · Indexação | o grafo de linhagem a partir dos eventos | indexador | queryFilter(BatchTransformed) |
| 15 · Atualização | uma atualização de implementação validada | atualizador | validateUpgrade, upgradeProxy |
1 · Configuração
Node.js 20 ou 22 LTS e npm. As dependências estão fixadas em package-lock.json (OpenZeppelin 5.3.0); use npm ci para que o build local seja o mesmo que a CI constrói.
git clone https://github.com/Arex-Corp/A-REX.git && cd A-REX
npm ci
npm run compile
npm test # 46 tests: reference implementation, hooks, extensions, invariants, this guide
npm run interface-ids # prints the ERC-165 ids; they must match the specification
Os trechos a seguir usam três pequenos auxiliares e nomeiam dez contas. explain converte um código de rejeição de volta em seu nome; a lista é o conjunto de códigos definidos em ARexCodes.
const commodity = (scheme, code) => ethers.keccak256(ethers.toUtf8Bytes(`${scheme}:${code}`));
const unit = (u) => ethers.encodeBytes32String(u);
const ref = (text) => ethers.keccak256(ethers.toUtf8Bytes(text));
const REASONS = Object.fromEntries(
["PAUSED", "FROZEN", "SENDER_INELIGIBLE", "RECEIVER_INELIGIBLE", "SANCTIONED", "BATCH_NOT_FOUND",
"BATCH_RETIRED", "BATCH_EXPIRED", "BATCH_NOT_PRODUCED", "ATTESTATION_MISSING", "SEASON_CLOSED",
"IN_TRANSIT", "LIMIT_EXCEEDED", "DOCUMENT_MISSING"].map((n) => [ethers.keccak256(ethers.toUtf8Bytes(`AREX:${n}`)), n]),
);
const explain = (code) => (code === ethers.ZeroHash ? "OK" : REASONS[code] ?? code);
const [admin, issuer, farmer, buyer, coop, mill, warehouse, certifier, enforcer, custodian] = await ethers.getSigners();
2 · Implantação
O token de referência é atualizável via UUPS, portanto é implantado atrás de um proxy ERC-1967 com o plugin de atualizações do OpenZeppelin, que também valida o layout de armazenamento. O registro de atestações é um contrato simples vinculado ao token.
const ARexToken = await ethers.getContractFactory("ARexToken");
const token = await upgrades.deployProxy(ARexToken, [admin.address, "ipfs://<cid>/{id}.json"], { kind: "uups" });
await token.waitForDeployment();
const attestations = await (await ethers.getContractFactory("ARexAttestations"))
.deploy(admin.address, await token.getAddress());
Pela linha de comando, a mesma pilha, mais os hooks de elegibilidade e de validade, é implantada com:
npx hardhat node # terminal 1
npx hardhat run scripts/deploy-standard.js --network localhost # token, attestations, 2 hooks
npx hardhat run scripts/deploy-full-standard.js --network localhost # every hook in order + extensions
ou com o Hardhat Ignition:
npx hardhat ignition deploy ignition/modules/ARexToken.js --network localhost \
--parameters '{"ARexTokenModule":{"baseURI":"ipfs://<cid>/{id}.json","allowPreHarvestTransfers":true}}'
Para uma rede de teste ou uma L2, hardhat.config.js define sepolia, polygon e arbitrum; defina a URL de RPC e PRIVATE_KEY em .env (modelo em .env.example). ARexToken compila para 24,136 bytes com optimizer.runs = 1, abaixo do limite de 24,576 bytes do EIP-170.
3 · Papéis
initialize(admin, baseURI) concede todos os papéis a admin, o que é conveniente para uma primeira implantação e inadequado para produção. Conceda cada papel operacional ao serviço ou ao multisig que o exerce:
await token.grantRole(await token.ISSUER_ROLE(), issuer.address);
await token.grantRole(await token.TRANSFORMER_ROLE(), mill.address);
await token.grantRole(await token.STATUS_ROLE(), warehouse.address);
await token.grantRole(await token.ENFORCER_ROLE(), enforcer.address);
| Papel | Permite | Titular recomendado |
|---|---|---|
DEFAULT_ADMIN_ROLE | conceder e revogar todos os papéis; setBaseURI | multisig de governança atrás de um timelock |
UPGRADER_ROLE | substituir a implementação | o mesmo timelock |
HOOK_ADMIN_ROLE | registerHook, removeHook | o mesmo timelock (as mudanças de hooks se tornam visíveis antes de serem aplicadas) |
ENFORCER_ROLE | setFrozenTokens, forcedTransfer | multisig de enforcement (conformidade e jurídico) |
PAUSER_ROLE | pause, unpause | chave de resposta a incidentes |
ISSUER_ROLE | createBatch | serviço de originação |
STATUS_ROLE | setBatchStatus, setBatchMetadata | armazém, inspetor ou registro |
TRANSFORMER_ROLE | transformações de processamento (alteração de commodity, unidade ou quantidade) | processadores |
ATTEST_ADMIN_ROLE (em ARexAttestations) | setTrustedIssuer, revogar qualquer atestação | autoridade de certificação ou plataforma |
Quando todos os papéis tiverem seus titulares, o implantador renuncia ao que deixou de ser necessário com renounceRole(role, deployer).
4 · Criar um lote
Um lote é um identificador ERC-1155. Seu registro on-chain é pequeno e fixo; tudo o que é descritivo fica em um documento de metadados cujo hash é registrado on-chain (esquema: standard/schema/batch-metadata.schema.json).
const metadata = { schemaVersion: "1.0.0", commodity: { scheme: "HS", code: "100199" }, unit: "KGM", quantity: 250000 };
const canonical = JSON.stringify(metadata); // RFC 8785 canonicalization in production
const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(canonical));
const uri = "ipfs://<cid>/batch-1.json"; // wherever you stored `canonical`
const init = {
commodity: commodity("HS", "100199"), // wheat
unit: unit("KGM"),
producedAt: Math.floor(Date.parse("2026-03-18T00:00:00Z") / 1000), // 0 = not yet harvested
expiresAt: 0, // 0 = no expiry
originRef: ref("DDS-2026-AR-000123"), // a reference, never coordinates
metadataHash,
uri,
};
const id = await token.connect(issuer).createBatch.staticCall(init, 250_000, farmer.address, "0x");
await (await token.connect(issuer).createBatch(init, 250_000, farmer.address, "0x")).wait();
createBatch.staticCall retorna o identificador que a transação atribuirá; os identificadores começam em 1 e aumentam de um em um. O token rejeita:
| Condição | Erro |
|---|---|
o chamador não tem ISSUER_ROLE | AccessControlUnauthorizedAccount(account, role) |
to é o endereço zero | ARexInvalidRecipient() |
quantity == 0 | ARexInvalidBatch("ZERO_QUANTITY") |
commodity == 0 ou unit == 0 | ARexInvalidBatch("COMMODITY_REQUIRED") / ("UNIT_REQUIRED") |
as duas datas definidas e expiresAt <= producedAt | ARexInvalidBatch("EXPIRY_BEFORE_PRODUCTION") |
pausado, destinatário inelegível ou um hook rejeita OP_MINT | EnforcedPause(), ERC7943CannotReceive(to), ARexHookRejected(hook, code) |
Convenções dos campos:
| Campo | Convenção | Exemplo |
|---|---|---|
commodity | keccak256("<scheme>:<code>"), com scheme em HS, FAO, GTIN, UNSPSC ou um esquema próprio | keccak256("HS:100199") trigo |
unit | código da Recomendação 20 da UN/CEFACT como bytes32 | "KGM", "TNE", "LTR", "BX" |
producedAt | segundos Unix; 0 para um lote ainda não colhido | venda a termo, recebível de safra |
expiresAt | segundos Unix; 0 para sem validade | produtos perecíveis |
originRef | hash de uma referência às evidências de origem; nunca coordenadas | referência da DDS do EUDR, identificador de registro |
metadataHash | keccak256 do documento de metadados canônico (RFC 8785) | |
uri | onde o documento está; manter curta, pois é armazenada on-chain | ipfs://<cid>, GS1 Digital Link |
5 · Ler um lote
const batch = await token.batchOf(id); // reverts ARexBatchNotFound if unknown
await token.batchExists(id); // true / false, never reverts
await token["totalSupply(uint256)"](id); // units in circulation (overloaded name)
await token.parentsOf(id); // [] for an original lot
await token.uri(id); // batch URI, or the base URI if empty
await token.isExpired(id); // reference-implementation helper
totalSupply é sobrecarregado no ERC-1155 Supply (totalSupply() e totalSupply(uint256)), portanto, no ethers v6, é preciso chamá-lo pela assinatura. Batch.status é 0 (ACTIVE), 255 (RETIRED) ou um valor definido pelo emissor entre esses dois.
6 · Transferência
Consulte sempre antes. transferCheck reproduz o pipeline de transferência como uma view e retorna o primeiro código de rejeição; 0x00…00 significa permitido. canTransfer é a versão booleana do ERC-7943 da mesma resposta.
const code = await token.transferCheck(farmer.address, buyer.address, id, 10_000);
if (code !== ethers.ZeroHash) throw new Error(`transfer blocked: ${explain(code)}`);
await token.connect(farmer).safeTransferFrom(farmer.address, buyer.address, id, 10_000, "0x");
A ordem do pipeline é fixa, de modo que o código indica qual regra falhou:
| Ordem | Verificação | Código retornado por transferCheck | Revert na transação |
|---|---|---|---|
| 1 | token pausado | AREX:PAUSED | EnforcedPause() |
| 2 | remetente elegível (todo hook ELIGIBILITY) | AREX:SENDER_INELIGIBLE | ERC7943CannotSend(from) |
| 3 | destinatário elegível | AREX:RECEIVER_INELIGIBLE | ERC7943CannotReceive(to) |
| 4 | o lote existe | AREX:BATCH_NOT_FOUND | ARexBatchNotFound(id) |
| 5 | o lote não foi baixado | AREX:BATCH_RETIRED | ARexBatchRetired(id) |
| 6 | amount <= balance − frozen | AREX:FROZEN | ERC7943InsufficientUnfrozenBalance(from, id, amount, unfrozen) |
| 7 | todo hook CHECK, na ordem de registro | o código do hook | ARexHookRejected(hook, code) |
A view e a transação avaliam na mesma ordem, de modo que o código sempre nomeia a primeira verificação em que a transação falha.
Dois detalhes importam na prática. AREX:FROZEN também é retornado quando o remetente simplesmente não possui unidades suficientes, porque a verificação incide sobre o saldo não congelado. E transferCheck passa data vazio aos hooks; um hook que lê os dados da transferência (como a liquidação do DeliveryHook) pode responder de forma diferente para a transação real.
7 · Linhagem: dividir, unir, processar
transform é a única forma de produzir um lote a partir de lotes. Ele queima as entradas de from, cunha as saídas para to, registra parentsOf(output) = inputIds e emite um único BatchTransformed, que corresponde a um TransformationEvent do EPCIS. transform.staticCall retorna os novos identificadores.
Um reagrupamento mantém a commodity, a unidade e a quantidade total, e não exige papel, apenas a titularidade ou a aprovação do ERC-1155. Dividir um lote em dois para um comprador:
const [lotA, lotB] = await token.connect(farmer).transform.staticCall(
farmer.address, buyer.address, [id], [400], [init, init], [250, 150], ref("SALE-42"));
await token.connect(farmer).transform(
farmer.address, buyer.address, [id], [400], [init, init], [250, 150], ref("SALE-42"));
Unir dois lotes em um lote agrupado para uma cooperativa:
const [pooled] = await token.connect(buyer).transform.staticCall(
buyer.address, coop.address, [lotA, lotB], [250, 150], [init], [400], ref("POOL-7"));
await token.connect(buyer).transform(
buyer.address, coop.address, [lotA, lotB], [250, 150], [init], [400], ref("POOL-7"));
O processamento altera a commodity, a unidade ou a quantidade, de modo que exige TRANSFORMER_ROLE, além da aprovação. O moinho transforma 1,000 kg do trigo do produtor em 750 kg de farinha:
const now = (await ethers.provider.getBlock("latest")).timestamp;
const flourInit = { ...init, commodity: commodity("HS", "110100"), producedAt: now };
await token.connect(farmer).setApprovalForAll(mill.address, true);
const [flour] = await token.connect(mill).transform.staticCall(
farmer.address, mill.address, [id], [1_000], [flourInit], [750], ref("MILL-RUN-7"));
await token.connect(mill).transform(
farmer.address, mill.address, [id], [1_000], [flourInit], [750], ref("MILL-RUN-7"));
| Regra | Garantida por |
|---|---|
o chamador é from ou foi aprovado por from | transform |
| toda entrada existe, não foi baixada e tem saldo não congelado suficiente | _update |
| reagrupamento: mesma commodity e mesma unidade, Σ entradas = Σ saídas | nenhum papel necessário |
| qualquer outro caso | TRANSFORMER_ROLE, caso contrário AccessControlUnauthorizedAccount |
| os hooks veem cada etapa | OP_TRANSFORM para queimas e cunhagens |
A linhagem construída por este guia:
flowchart LR
B1["#1 trigo 250,000 kg<br/>produtor"] -->|"divisão · SALE-42"| B2["#2 trigo 250"]
B1 -->|"divisão · SALE-42"| B3["#3 trigo 150"]
B2 -->|"união · POOL-7"| B4["#4 trigo 400<br/>cooperativa"]
B3 -->|"união · POOL-7"| B4
B1 -->|"processamento · MILL-RUN-7"| B5["#5 farinha 750<br/>moinho"]8 · Status e metadados
Somente STATUS_ROLE altera o status; possuir unidades não confere nada. Os valores 1..254 são definidos pelo emissor; o significado deles deve ser publicado no documento de metadados (status.table). Atualizar os metadados substitui o hash e a URI e emite BatchMetadataUpdated, de modo que o histórico de documentos permanece verificável.
await token.connect(warehouse).setBatchStatus(flour, 1, ref("IN_STORAGE")); // 1..254 are yours
const labReport = { ...metadata, lab: { moisture: 14.2 } };
await token.connect(warehouse).setBatchMetadata(
flour, ethers.keccak256(ethers.toUtf8Bytes(JSON.stringify(labReport))), "ipfs://<cid>/flour-lab.json");
9 · Hooks
Os hooks são contratos independentes que o token chama na ordem de registro. Aqui, o hook de elegibilidade é executado primeiro, depois o de validade e, por fim, uma regra segundo a qual o café precisa de uma declaração de devida diligência do EUDR atestada antes de poder ser vendido.
const eligibility = await (await ethers.getContractFactory("EligibilityHook")).deploy(admin.address);
const expiry = await (await ethers.getContractFactory("ExpiryHook")).deploy(true); // allow pre-harvest
const attestationHook = await (await ethers.getContractFactory("AttestationHook"))
.deploy(admin.address, await attestations.getAddress());
const COFFEE = commodity("HS", "090111");
const EUDR_DDS = ref("AREX:EUDR:DDS");
await attestationHook.setRequirement(COFFEE, EUDR_DDS); // coffee needs a due-diligence statement
await token.registerHook(await eligibility.getAddress()); // evaluated first
await token.registerHook(await expiry.getAddress());
await token.registerHook(await attestationHook.getAddress()); // evaluated last
await token.getHooks(); // the ordered list
| Hook | Flags | Construtor | Configurar com |
|---|---|---|---|
EligibilityHook | CHECK, ELIGIBILITY | (admin) | setAllowlistEnabled, setAllowlisted, setBlocklisted, setSanctioned |
ExpiryHook | CHECK | (allowPreHarvestTransfers) | — (lê o registro do lote) |
AttestationHook | CHECK | (owner, attestationRegistry) | setRequirement(commodity, topic) |
PerishabilityHook | CHECK | (admin) | setCommodityDecay, setBatchDecay |
SeasonalHook | CHECK | (admin) | addWindow(commodity, op, start, end), clearWindows |
DeliveryHook | CHECK, EXECUTED | (admin) | schedule, pickUp, markInTransit, recordReading, confirmDelivered, dispute, resolve |
TraceabilityHook | EXECUTED | (admin, persist) | recordEvent (RECORDER_ROLE) |
O registro rejeita EOAs, contratos sem suporte a ERC-165 de IARexHook e hooks cujas flags são 0. Coloque primeiro os hooks baratos e que rejeitam com frequência: uma rejeição interrompe o pipeline. Para escrever um hook próprio: docs/HOOK-DEVELOPMENT.md.
10 · Atestações
Uma atestação é (batch, topic) → (issuer, reference, issuedAt, expiresAt, revoked), válida apenas enquanto seu emissor for confiável para o tópico. Com o requisito da etapa 9 em vigor, o lote de café não pode se mover até que a declaração seja atestada:
const coffee = await token.connect(issuer).createBatch.staticCall({ ...init, commodity: COFFEE }, 60, farmer.address, "0x");
await token.connect(issuer).createBatch({ ...init, commodity: COFFEE }, 60, farmer.address, "0x");
explain(await token.transferCheck(farmer.address, buyer.address, coffee, 1)); // "ATTESTATION_MISSING"
await attestations.setTrustedIssuer(EUDR_DDS, certifier.address, true); // ATTEST_ADMIN_ROLE
await attestations.connect(certifier).attest(coffee, EUDR_DDS, ref("DDS-2026-AR-000123"), 0);
await attestations.isAttested(coffee, EUDR_DDS); // true
explain(await token.transferCheck(farmer.address, buyer.address, coffee, 1)); // "OK"
isAttested é falso quando não há atestação, quando ela foi revogada, quando expiresAt já passou ou quando o emissor deixou de ser confiável para o tópico. O emissor ou ATTEST_ADMIN_ROLE pode revogar. Os tópicos são valores bytes32 livres; use keccak256("<NAMESPACE>:<NAME>"), por exemplo AREX:EUDR:DDS ou AREX:CERT:ORGANIC_EU.
11 · Sanções e elegibilidade
EligibilityHook responde a canSend e canReceive para o token. Uma sanção registra sua fonte e uma validade opcional, de modo que uma atualização de lista possa expirar automaticamente.
await eligibility.setSanctioned(buyer.address, true, ethers.encodeBytes32String("OFAC-SDN"), 0);
explain(await token.transferCheck(farmer.address, buyer.address, id, 1)); // "RECEIVER_INELIGIBLE"
await eligibility.setSanctioned(buyer.address, false, ethers.encodeBytes32String("OFAC-SDN"), 0);
12 · Enforcement
Congelamento e transferências forçadas são a superfície de enforcement do ERC-7943, para gravames, ordens judiciais e apreensões. Um congelamento pode exceder o saldo atual e, nesse caso, cobre também depósitos futuros. Uma transferência forçada dispensa a elegibilidade e a verificação de saldo congelado, funciona com o token pausado, reduz o valor congelado ao que resta e ainda executa os hooks com OP_FORCED_TRANSFER.
await token.connect(enforcer).setFrozenTokens(buyer.address, id, 6_000); // may exceed the balance
await token.getFrozenTokens(buyer.address, id); // 6_000
explain(await token.transferCheck(buyer.address, farmer.address, id, 5_000)); // "FROZEN"
await token.pause(); // PAUSER_ROLE
await token.connect(enforcer).forcedTransfer(buyer.address, custodian.address, id, 6_000); // works while paused
await token.unpause();
Documente o procedimento segundo o qual ENFORCER_ROLE atua (ordem judicial, contrato de penhor, notificação de sanções) e mantenha o papel atrás de um multisig. Toda ação emite Frozen ou ForcedTransfer.
13 · Baixa
RETIRED (255) é terminal: nenhuma transferência, cunhagem ou transformação, e nenhum caminho de volta. A queima continua possível para que os bens entregues ou destruídos saiam da oferta.
await token.connect(warehouse).setBatchStatus(flour, 255, ref("DELIVERED")); // RETIRED is terminal
explain(await token.transferCheck(mill.address, buyer.address, flour, 1)); // "BATCH_RETIRED"
await token.connect(mill).burn(mill.address, flour, 750); // burning still works
14 · Indexação
O grafo de linhagem é a lista de eventos BatchTransformed; nada precisa ser reconstruído a partir de transferências individuais.
const transforms = await token.queryFilter(token.filters.BatchTransformed());
const edges = transforms.flatMap((e) =>
e.args.inputIds.flatMap((input) => e.args.outputIds.map((output) => [input, output, e.args.processRef])));
// edges: [1 → 2], [1 → 3], [2 → 4], [3 → 4], [1 → 5]
| Evento | Emitido por | Uso |
|---|---|---|
BatchCreated(id, issuer, commodity, unit, quantity, to) | createBatch | novo lote; ObjectEvent ADD do EPCIS |
BatchTransformed(operator, inputIds, inputAmounts, outputIds, outputAmounts, processRef) | transform | aresta de linhagem; TransformationEvent do EPCIS |
BatchStatusChanged(id, previousStatus, newStatus, reason) | setBatchStatus | ciclo de vida |
BatchMetadataUpdated(id, metadataHash, uri) | setBatchMetadata | histórico de documentos |
TransferSingle, TransferBatch | toda mudança de saldo | posições; ObjectEvent OBSERVE do EPCIS |
Frozen(account, tokenId, amount), ForcedTransfer(from, to, tokenId, amount) | enforcement | gravames, apreensões |
HookRegistered(hook, flags, position), HookRemoved(hook) | administração de hooks | mudanças de regras |
Attested, AttestationRevoked, TrustedIssuerUpdated | ARexAttestations | evidências |
15 · Atualização
O armazenamento usa namespace ERC-7201 (erc7201:arex.storage.token); acrescente campos a TokenStorage, sem nunca reordená-los nem removê-los. Valide antes de cada atualização; o plugin recusa layouts que corromperiam o armazenamento.
await upgrades.validateUpgrade(await token.getAddress(), ARexToken, { kind: "uups" });
await upgrades.upgradeProxy(await token.getAddress(), ARexToken); // UPGRADER_ROLE
Em produção, UPGRADER_ROLE fica atrás de um multisig e de um timelock, e a atualização é ensaiada antes em um fork da rede de destino.
Como testar a integração
npx hardhat test test/standard/ImplementationGuide.test.js # this chapter, executed
npx hardhat test test/standard/ARexToken.test.js # token behaviour and conformance
npx hardhat run scripts/smoke-standard.js # narrated walkthrough of one lot
npx hardhat run scripts/gas-report-standard.js # gas with 0, 1 and 2 hooks, and per hook
test/standard/helpers.js exporta deployStack(), batchInit(), as constantes OP, HOOK, STATUS e CODES e supply(), para testes próprios.
Rumo à produção: lista de verificação
- Build reprodutível (
npm ci), CI verde, identificadores de interface iguais aos da especificação. - Papéis redistribuídos conforme a etapa 3; papéis do implantador renunciados.
- Hooks auditados, ordem documentada,
getHooks()publicado. - Versão do esquema de metadados e tabela de status publicadas;
originRefe dados restritos livres de dados pessoais. - Instrumentos jurídicos referenciados nos metadados; classificação da oferta definida com assessoria jurídica.
- Auditoria de terceiros do token, do registro de atestações e de cada hook registrado (pacote:
docs/AUDIT-READINESS.md). - Atualização ensaiada em um fork com
validateUpgrade.