SonaCORE

Referência

Os seus produtos, como uma API.

Cada ação que os seus manifestos declaram vira uma operação documentada, com o corpo tipado a partir da própria declaração — e um cliente Python gerado do mesmo documento. O que você chama continua sendo a rota genérica; o que muda é o que está escrito sobre ela.

Lendo isto sem credencial
Esta página não tem conteúdo próprio: ela mostra os produtos do seu tenant, e por isso precisa de uma credencial aprovada com o escopo quadra/products. A referência da plataforma é pública e mostra as rotas genéricas que estas operações documentam. Pedir acesso.

Credenciais

sem conexão

Vincular um produto já atualiza este documento

Não existe artefato gerado, nem pipeline, nem gatilho. O documento é derivado a cada leitura a partir das linhas de vínculo do seu tenant, então vincular, versionar ou suspender um produto muda a próxima resposta — e não há nada que possa ficar desatualizado, porque não há nada guardado. O campo info.version é um resumo do que o documento descreve (produto, versão, hash do artefato, ciclo de vida), e não um relógio: ele muda quando um vínculo muda, e só então.

O documento descreve os produtos em DRAFT, CERTIFIED e LIVE. Um produto SUSPENDED aparece marcado como deprecated, porque ainda há instâncias abertas sobre as quais é preciso raciocinar; um produto RETIRED não aparece, porque não tem superfície de escrita nenhuma e documentá-lo seria anunciar chamadas que não podem dar certo.

Nada aqui é uma rota nova

Cada operação é uma rota genérica com o gabarito preenchido: o identificador do produto no caminho, e a ação como um segmento do caminho ou como um const no corpo. Então POST /v1/products/tnt.acme.savings-lock/instances é POST /v1/products/{product_id}/instances — a mesma rota que a referência da plataforma já documenta, atendida pelo mesmo código.

Isso é uma decisão de arquitetura, não uma limitação: uma superfície por produto seria infraestrutura por produto, e a cerimônia de expor uma rota — FastAPI, nginx, a tabela do gateway, o contrato — foi paga uma vez para todos os produtos de todos os tenants. Caminhos com o seu próprio vocabulário existem no documento; no fio, a chamada é genérica.

Os limites vêm do campo do datum, não do parâmetro

Um manifesto declara seis tipos de parâmetro e nada mais — cents, int, bool, hex, string, account_id. O que torna o esquema útil é o campo do datum que cada parâmetro preenche: é lá que penalty_bps declara 0..10000 (porque release_splits recusa qualquer outra faixa), e é lá que um campo de 32 bytes diz que aquele hex é uma chave de verificação.

Uma chave de 32 bytes tem de ser de uma conta real
Nenhuma leitura publica a chave de uma conta — publicar chaves por uma leitura tornaria o material criptográfico de todo tenant uma superfície pública para resolver um campo. Trinta e dois bytes zerados codificam perfeitamente e produzem uma instância que ninguém consegue assinar, ou pior, um encerramento que paga o saldo inteiro para uma chave que ninguém tem. O documento marca esses parâmetros; o valor tem de vir do seu backend.

Um detalhe que o documento diz em voz alta em vez de fingir: parâmetros não declarados são ignorados pelo interpretador, não recusados. Seria mais bonito escrever additionalProperties: false, e seria uma recusa que nada executa — exatamente a classe de afirmação que a execução de conformidade existe para pegar.

O cliente gerado, e o único método que justifica gerá-lo

Um gerador de código qualquer produz nomes de método. O que ele não produz é wait_for_state, e é por isso que a plataforma gera este cliente. As escritas de produto são obrigação primeiro: a linha é gravada antes de a transação ser submetida, então uma ação que o seu validador recusa responde 200 e é recusada na cadeia um instante depois. Um cliente que confiasse no código HTTP relataria uma recusa como sucesso.

Julgar um passo tem duas condições, não uma: o estado declarado tem de aparecer na linha do tempo da instância e awaiting_index tem de ser falso naquela transição. Largar a segunda é uma corrida — product_state aparece assim que a linha é gravada, antes de o modelo de leitura projetar o UTxO, então a ação seguinte lê uma linha que não pode gastar e recebe 409. O roteiro curto passa e o longo falha de vez em quando, que é a pior forma de estar errado.

python
from quadra_products import QuadraProducts

q = QuadraProducts("https://api-sbx.sonacore.com.br",
                   client_id="...", client_secret="...")

body = q.tnt_acme_savings_lock_open(
    account_id="acc_...", amount=50000, fee_vk="...",
    deposit_slot=0, maturity_slot=0, maturity_at_ms=0, penalty_bps=200,
)
q.tnt_acme_savings_lock_wait_for_state(body["instance_id"], "LOCKED")

Um tempo esgotado levanta QuadraRefused e não QuadraFailed, deliberadamente: de fora, uma recusa e uma cabeça muito lenta são indistinguíveis, e afirmar qual das duas foi seria um palpite vestido de resultado.

Por que o documento não traz servers

O serviço fica atrás de um gateway e não conhece a própria URL pública — e uma URL que quem chama pudesse escolher, embutida num cliente que se baixa, seria um lugar para onde credenciais poderiam ser enviadas. Então o documento omite servers e o tokenUrl do OAuth é relativo, o que o resolve contra o host de onde você baixou o documento — que é o certo por construção. O cliente gerado pede base_url no construtor pelo mesmo motivo.

O documento é o seu, e provar isso exige dois tenants

O tenant vem da credencial, nunca de um segmento do caminho. As linhas de vínculo são endereçadas por TENANTPRODUCT#{tenant}#{produto}, então o produto de outro tenant é inendereçável aqui — não filtrado. Não existe uma linha no gerador onde um filtro pudesse ser esquecido, porque não existe filtro.

E é por isso que um ambiente local não prova nada disso: QUADRA_ENV=local sintetiza uma identidade sem tenant algum e com todos os escopos. Uma execução local verde prova que nada quebrou; não prova isolamento. Essa prova são os testes que vinculam dois tenants de verdade, e o roteiro que roda contra o ambiente hospedado com duas credenciais distintas.