Decionis 0.3.0
dotnet add package Decionis --version 0.3.0
NuGet\Install-Package Decionis -Version 0.3.0
<PackageReference Include="Decionis" Version="0.3.0" />
<PackageVersion Include="Decionis" Version="0.3.0" />
<PackageReference Include="Decionis" />
paket add Decionis --version 0.3.0
#r "nuget: Decionis, 0.3.0"
#:package Decionis@0.3.0
#addin nuget:?package=Decionis&version=0.3.0
#tool nuget:?package=Decionis&version=0.3.0
Decionis .NET SDK
The Decionis .NET SDK is an execution interceptor for .NET 8 services. It captures execution intent, delegates policy judgment to Decionis, and runs the callback only when the signed decision allows or when shadow mode is explicit.
dotnet add package Decionis
var client = new DecionisClient(Environment.GetEnvironmentVariable("DECIONIS_API_KEY")!);
var result = await client.EnforceAsync(
new DecisionRequest
{
TenantId = "trading_client_001",
Actor = new() { ["id"] = "cfd_bot_7", ["type"] = "TRADING_BOT" },
Action = new() { ["type"] = "OPEN_POSITION", ["instrument"] = "BTC-PERP" },
PolicyRefs = ["cfd-risk-policy"]
},
async decision => await broker.OpenPositionAsync(order, decision.DecisionId));
Use EvaluateAsync for diagnostics. Use EnforceAsync(..., execute, shadow) for
production execution boundaries so the callback runs only after Decionis returns
ALLOW.
var shadow = await client.EnforceAsync(request, ExecuteTransferAsync, shadow: true);
The package includes ASP.NET Core middleware hooks and Ed25519 decision signature verification.
Execution authority: no grant, no execution
Decionis.Authority.ExecutionAuthorityClient runs a consequential action only
after Decionis authorizes that exact intent and issues a single-use grant, and
only after you claim that grant. A changed amount, account or target is a
different intent, and its grant does not claim.
| Step | Call | Route |
|---|---|---|
| Ask for a grant | IssueGrantAsync(request) |
POST /v1/authority/grants |
| Check it without using it | VerifyGrantAsync(token, request) |
GET /v1/authority/grants/{grantId} |
| Claim it once, bound to the exact parameters | ClaimGrantAsync(token, request) |
POST /v1/authority/grants/{grantId}/claims |
| Record what happened | RecordReceiptAsync(receipt) |
POST /v1/authority/grants/{grantId}/receipts |
| Follow a managed Presence escalation | GetEscalationAsync, WaitForAuthorizationAsync |
GET /v1/authority/escalations/{escalationId} |
EnforceAndExecuteAsync runs the whole sequence and fails closed:
using Decionis.Authority;
var authority = new ExecutionAuthorityClient(new ExecutionAuthorityClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("DECIONIS_API_KEY"),
RequestSigner = ExecutorRequestSigner.FromPkcs8Pem("payments-executor-1",
File.ReadAllText("/run/secrets/executor-key.pem")),
});
var request = AuthorityRequest.Bind(new AuthorityIntent
{
TenantId = orgId,
Actor = new AuthorityActor("ap-bot", "SERVICE"),
Action = new AuthorityAction("create_vendor_payment", "erp:vendor:V-1009",
new { amount = 18000.50m, currency = "EUR", vendor_id = "V-1009" }),
DownstreamTarget = new DownstreamTarget("sap", "create_payment"),
});
var run = await authority.EnforceAndExecuteAsync(request, async (grant, ct) =>
{
using var payment = new HttpRequestMessage(HttpMethod.Post, "https://ledger.example/v1/payments")
{
Content = new StringContent(CanonicalJson.JcsText(request.ActionParameters()), Encoding.UTF8, "application/json"),
};
await grant.SignAsync(payment, ct);
return await http.SendAsync(payment, ct);
});
if (!run.Executed)
{
// BLOCKED_BY_POLICY, NO_EXECUTION_GRANT, GRANT_NOT_REDEEMABLE or AUTHORITY_ERROR
logger.LogWarning("Payment not released: {Reason}", run.BlockReason);
}
- The action runs at most once, inside the claimed grant's lease. Send the
request to the system of record through
grant.SignAsync(...), so it can refuse anything this grant did not authorize (next section). - If the action throws, an
INDETERMINATEreceipt is recorded and the exception propagates. Classify a certain failure withnew ExecutionOptions<T> { ClassifyFailure = _ => ReceiptOutcome.FAILED }. - A returned value is digested as RFC 8785 JSON into the receipt's effect
evidence. A Protocol 1.1 grant records the receipt without that
confirmation and notes
PROTOCOL_1_1_EFFECT_EVIDENCE_REQUIRED. SHADOWandPARALLELrequests observe only: they never authorize.- With
escalation: new ManagedEscalationRequest(), anESCALATEdecision carries a managed-escalation locator.WaitForAuthorizationAsyncpolls it until Presence approves (GRANT_READY, with the decision whose grant you claim) or the intent expires.
Numbers are hashed as Decionis reads them. A long beyond 2^53 or a decimal
with more precision than a double holds is refused with
DECIONIS_INTENT_NUMBER_INEXACT, so the amount Decionis authorizes is the
amount you send. Pass such values as strings. The intent hash, execution
payload digest and grant id are held to the shared vectors in
sdks/test/authority-vectors.json, the same
ones @decionis/sdk and the Decionis server pass.
In ASP.NET Core, register the client with an IHttpClientFactory-managed
HttpClient:
builder.Services.AddDecionisExecutionAuthority(options =>
{
options.ApiKey = builder.Configuration["Decionis:ApiKey"];
options.RequestSigner = ExecutorRequestSigner.FromPkcs8Pem(
builder.Configuration["Decionis:ExecutorKeyId"]!, builder.Configuration["Decionis:ExecutorKeyPem"]!);
});
Sign the request to the system of record
A system of record that implements the Verifying Provider Profile at VP-2 effects a request only when all of these hold:
- it is signed with an executor key the system of record issued or registered for you (RFC 9421 message signature with an RFC 9530 content digest);
- the signature covers the grant id, the decision id, the intent hash, the idempotency key and the claim attestation;
- the attestation verifies against
https://api.decionis.com/.well-known/decionis-execution-grant-jwks.jsonand describes this request: this grant, this decision, this intent, and a body whose canonical JSON is the parameters the grant was claimed for; - the grant has not been presented before.
grant.SignAsync(request, ct) signs an HttpRequestMessage in place over the
claimed grant. Forwarding ClaimAttestation on its own is not enough: a VP-2
provider refuses it with SIGNATURE_INVALID_OR_INCOMPLETE.
Make an Ed25519 key and give the public half to whoever runs the system of record. They register it under a key id and tell you that id.
openssl genpkey -algorithm ed25519 -out executor-key.pem openssl pkey -in executor-key.pem -pubout -out executor-key.pub.pemSet
RequestSigner = ExecutorRequestSigner.FromPkcs8Pem(keyId, pem), ornew ExecutorRequestSigner(keyId, seed)for the 32-byte private key you already hold. The key never leaves your service.In the action, send the parameters you authorized as the JSON body, with an absolute request URI, and call
grant.SignAsync(...)before sending. Key order and whitespace are yours to choose; the system of record compares canonical JSON.
The signed request carries:
| Header | Value |
|---|---|
idempotency-key |
the intent id |
x-agent-safe-intent-hash |
the intent hash the grant was issued for |
x-agent-safe-grant-id, x-agent-safe-decision-id |
the claimed grant and the decision that issued it |
x-agent-safe-claim-attestation |
the claim attestation |
content-digest |
sha-256=:<base64>: over the exact body bytes |
signature-input, signature |
label agentsafe, alg="ed25519", your keyid |
The signer replaces any header of the same name already on the request, and
sends the body bytes it digested with the content headers you set. The query
string is not covered, so keep anything that changes the effect out of it. For
a request built some other way, take the same headers from
grant.SignedHeaders(method, uri, body) and send body exactly. For a request
outside EnforceAndExecuteAsync, call signer.SignAsync(request, new ExecutorDispatch(...)),
or ExecutorDispatch.Read(...) for a read that changes nothing.
The signer is held to the profile's own vectors
(sdks/test/verifying-provider), and its tests
send signed requests over HTTP to the AgentSafe ASP.NET Core verifier
(Decionis.VerifyingProvider), which accepts them once and refuses a replay or
an altered body.
If you would rather not hold an executor key in each service, run the
AgentSafe executor
as a gateway in front of your systems of record, with
DOWNSTREAM_CREDENTIAL_KIND=SIGNED_REQUEST: it claims the grant and signs
every dispatch itself.
ASP.NET Core
app.UseDecionisGate(
client,
http => new DecisionRequest
{
Actor = new() { ["id"] = "orders-api", ["type"] = "SERVICE" },
Action = new() { ["type"] = "HTTP_REQUEST", ["path"] = http.Request.Path.Value },
PolicyRefs = ["api-execution-policy-v1"]
});
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- BouncyCastle.Cryptography (>= 2.4.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0 | 116 | 10/30/2026 |