.NET SDK reference
Package-level reference for installation, public interfaces, observation boundaries and delivery behaviour.
#Package and basic usage
Sigillum Finans .NET SDK 0.11.0
.NET 8, 9 and 10 use this normal net8.0 NuGet package and the same shared
Java/.NET/Python observation contract. Official OpenTelemetry dependencies
are mandatory package dependencies. Distribution is commercially licensed;
public registry publication is separate from this package build.
#Normal ASP.NET Core setup
Reference Sigillum.Finans.Sdk.AspNetCore from the supplied local NuGet source;
it references the core Sigillum.Finans.Sdk normally. The one .NET delivery ZIP
contains the two Sigillum packages. NuGet resolves their declared third-party
dependencies through the institution's normal package mirror. Worker/console projects use
only the core package and do not require the ASP.NET Core shared runtime. Put the
service-specific ready configuration at
AppContext.BaseDirectory/.sigillum/config.json, or provide an explicit
absolute configuration path. Add the normal package registration:
using Sigillum.Finans.Sdk;
builder.Services.AddSigillum();
builder.Services.AddHttpClient();
// After building the application, before handlers that consume request bodies:
app.UseSigillum();The registration connects a ready handler to clients created by
IHttpClientFactory; the middleware observes inbound ASP.NET Core requests.
It does not require a second customer-owned helper project or hidden HTTP
profile. A directly constructed HttpClient uses the supplied handler:
var client = new HttpClient(new SigillumHttpHandler(observer)
{
InnerHandler = new HttpClientHandler()
});Unread inbound requests are complete and empty only when native HTTP/1.0 or HTTP/1.1 framing proves no body: no Transfer-Encoding, and no Content-Length or one valid zero length. Finalization also requires no observed bytes/chunks or capture loss. The HTTP method alone never proves emptiness. Duplicate, conflicting or invalid framing, HTTP/2, HTTP/3 and unknown protocol metadata remain incomplete until native consumption proves completion. The SDK never reads, drains or rewinds a request to make this determination.
For a worker or batch, start the SDK once and use a configured named entry:
using var observer = new SigillumClient(absoluteConfigPath);
using var operation = observer.BeginOperation("configured-execution-name");
// Existing native work continues here.Configuration, trust preparation and delivery run on the background worker.
The business path does not wait for readiness, network, disk, a receipt or a
flush. Dispose stops new capture without waiting for pending delivery.
Calls made before configuration becomes ready are reported as observation
loss; they are never presented as captured. Status distinguishes readiness,
pending memory, verified durable custody, loss and retry attempts.
#Configuration
The shared sigillum.sdk-config/2 file contains these fields:
| Field | Meaning |
|---|---|
formatVersion |
sigillum.sdk-config/2 |
institutionId, environmentId, serviceId |
Actual service scope; nonzero UUIDs |
sourceToken |
This service's own source credential |
transferEndpoint |
Exact HTTPS /api/transfer URL |
rootCertificatePem |
Installation public CA; hostname checks remain enabled |
custodyTrust |
keyId, spkiBase64, sha256Pin for the pinned P-256 custody receipt key |
scopeTrust |
Pinned P-256 public key verifying product-issued execution scopes |
contextKeyBase64 |
32-byte institution/environment propagation key |
entryPoints |
Actual starts: applicationId, entryPointId, kind, operation, path, signed scopeToken |
limits |
maxQueuedBytes, maxQueuedRecords, requestTimeoutMs, retryDelayMs, chunkBytes |
localTransfer |
Optional product Agent endpoint (http://127.0.0.1:<port>) and a distinct 32–256 character printable ASCII token without spaces |
With localTransfer, the SDK's observer worker sends bounded SFRAW001 raw
captures to the installed Sigillum Agent at /v1/captures. Attachment hashes,
observation envelopes, SFREC001 packaging, encryption, durable retry and
upstream TLS run in that separate product process. Private official OTEL
lifecycle and export still run on the SDK observer worker. Neither native
capture nor application startup waits for the Agent. An unavailable local
Agent retains the same bounded RAM retry behavior; it never activates a remote
delivery fallback in the institution process. Failed local handoffs back off
the endpoint with bounded exponential delay and small jitter, starting at the
configured retry delay and capped at 30 seconds (or a larger configured delay).
A valid local receipt resets the failure streak; direct-mode retries retain
their existing configured fixed delay.
The exact HMAC-authenticated localDurable receipt releases pending SDK RAM
only after the Agent's durable local handoff. Status.LocalDurableRecords
counts these handoffs separately from Status.DurableRecords, which continues
to count remote custody in direct mode. A local receipt is not final central
evidence acceptance. Signed profiles come through the same Agent's
/v1/profiles route and retain the existing scope signature and revision
checks. Configuration without localTransfer retains direct HTTPS delivery.
An execution entry uses kind: execution, an exact operation and no path.
An HTTP entry uses kind: http, an exact HTTP method and exact path.
An already connected shared service can use entryPoints: []: its actual
application and execution arrive in the signed observer carrier. Without a
valid carrier or matching entry the SDK does not guess an application.
Each entry carries a product-signed, immutable application/service scope. The SDK verifies its P-256 signature, pinned key, institution/environment, actual entry identity and bounded service set on its worker before enabling roots. There is no unsigned or version-1 configuration fallback. Every root admission freezes that scope token and its UTC start time. Existing executions keep their original scope through all children and retries.
The background worker refreshes source profiles every 30 seconds from the
same-origin authenticated /api/transfer/profiles endpoint. TLS, timeout and
the 1 MiB response bound remain enforced. Only a fully verified monotonic
registry revision replaces the active entry snapshot. The refresh is independent
of delivery retry; business methods never perform configuration network I/O.
Known source revocation stops new roots without rewriting accepted work.
A connected service excluded from an incoming execution's signed service set
does not create spans, HTTP records, AI facts or reference records. It preserves
the incoming observer span and can forward the authenticated carrier to the
next connected service. Its returned scope has IsObserved == false; dispose
it normally so that the previous ambient context is restored. This applies to
BindContext and both synchronous and asynchronous HTTP calls. Inclusion in
another application's scope does not expand the current application's scope.
Default limits are 64 MiB, 1024 records/active scopes, a 5000 ms worker request timeout, 1000 ms retry delay and 65536-byte stream chunks. Active private OTEL spans share the finite memory/slot budget. Pending encoded delivery and encoding workspace are included in conservative memory reservations. Queue capacity may be lowered, not raised above the shared 64 MiB/1024 ceiling.
#Public types and methods
SigillumClient is the service-wide observer. SdkOptions, SdkLimits,
SdkEntryPoint and ReceiptTrust are the typed configuration alternatives.
SdkOperation is a disposable execution/step scope; its Context is a
detached public SdkContext snapshot containing actual execution and span IDs
and the frozen ScopeToken. Reference arrays returned by Current or
Context are detached from active and queued SDK records.
| Method/property | Use |
|---|---|
BeginOperation(name) |
Begin a configured worker/batch execution; no login/session is required |
BeginHttpOperation(method, path, incomingContext) |
Ready inbound HTTP boundary used by the middleware |
BeginStep(name) |
Create a real child of the current observer scope |
SdkOperation.Fail(exception) |
Record safe error type/cancellation; never read a virtual exception message |
Current, ExportContext(), BindContext(carrier) |
Inspect/export/bind the signed independent observer context |
SetReferences(SdkReferences) |
Add optional session/person/external references without merging executions |
TryRecord(family, kind, rawJson, attachments, schema, links) |
Generic actual fact with an extensible raw JSON object |
Model, Classification, Retrieval, Tool, Embedding |
Named AI fact helpers accepting raw JSON |
Inference, Evaluation, Policy, Explanation |
Prediction/control/source-produced explanation helpers |
Source, Custom |
Source comparison/custom fact plus optional exact content |
ObserveAiAsync(name, rawInput, nativeCall, token) |
Invoke one native call once; capture supplied input and returned raw output; preserve the native exception/cancellation |
TrySetOtelAttribute, TryAddOtelEvent, TryAddOtelLink |
Bounded enrichment of the SDK's real private OTEL span |
Status |
Ready, queued records/bytes, remote custody records, local durable handoffs, lost records, failed attempts and last observer error |
The generic families are lifecycle, ai, http, otel, context, source
and custom. Their contents retain actual field names, absent fields,
explicit nulls, arbitrary precision numeric lexemes and extension fields.
The returned capture boolean acknowledges bounded RAM admission only. Raw JSON
object/depth/duplicate validation runs on the observer worker. Invalid admitted
data is never delivered; its reservation is released and an explicit encoding
loss report is queued under the original frozen execution scope.
The SDK does not call arbitrary object getters, enumerate lazy application
objects or infer a model's hidden reasoning. Use already materialized UTF-8
JSON and arrays, rather than application object serialization.
observer.Classification("""
{"intent":"actual-source-intent","score":0.9876543210987654321}
"""u8.ToArray());
observer.SetReferences(new SdkReferences(
SessionRef: existingSessionReference,
PersonRef: existingPersonReference,
External: [new("institution.trace", existingTraceReference)]));SdkAttachment(Id, MediaType, Purpose, Bytes) carries exact binary/text bytes.
SdkLink(TraceId, SpanId, Relation) carries actual explicit links.
Content bytes are copied under the admission budget, then hash/length/exact-byte
references are generated by the worker. Standard capture allows JSON objects
up to 8 MiB minus envelope overhead, depth 32, up to 128 attachments of at most
16 MiB each, subject to the total 32 MiB packet and finite queue budget.
Oversize, malformed or saturated observations fail capture and report loss;
they do not throw into native work or silently become complete records.
OTEL attribute helpers accept known scalar or homogeneous primitive-array
JSON values. Events and links accept JSON objects of those attributes. These
use OTEL's actual integer/double value types; use the generic raw JSON fact
surface when exact arbitrary-precision number lexemes are required. Each
helper reserves memory before retaining values, releases replacement credit,
and reports capacity rejection. Limits are 1024 attributes, 1024 events,
1024 links and 4096 primitive array elements, always subject to the shared
byte budget. No independent source string truncation is applied. Up to 64
external references are accepted. No raw private Activity is exposed.
#Automatic capture and delivery
Supported HTTP boundaries capture method, target, headers, response status,
actual consumed request/response bytes, stream chunks/offsets/terminal state,
safe error/cancellation type and actual ancestry. Bodies are not read ahead.
Both HttpClient.Send and SendAsync use the same capture contract and invoke
the native transport once. Native synchronous content serialization and stream
access remain synchronous. Successful stream repositioning or resizing is
marked partial; a reposition after an earlier terminal appends an explicit
loss fact rather than rewriting history.
Header capture allows 4096 name/value pairs and 1 MiB total UTF-8 names/values;
over-capacity output explicitly states complete: false and reports loss.
Header snapshots and JSON workspace reserve the same finite ledger before
copying. A wrapped content object's header-copy credit remains held until
the application disposes that content. If credit is unavailable, the native
content remains unchanged and the observation loss stays explicit.
Disposal before EOF, capacity loss and incomplete consumption remain partial.
The native value, exception, cancellation token and stream ownership remain
with the application. Existing traceparent/tracestate headers and native
OTEL providers, samplers/exporters and Activity.Current are preserved.
Each SDK instance owns an always-on private OTEL provider/source with its own
configuration, independent of institution OTEL environment/sampler settings.
All official Activity start/stop and listener callbacks run on the observer
worker. The business path creates only bounded product execution/span context
and immutable facts. A slow global institution listener can stall observation
delivery but cannot enter the business call through this instrumentation.
Actual private spans include resource, instrumentation scope, attributes,
events, links, status and actual dropped/observation limit counts. Their
real OTEL IDs are explicitly exposed as nativeContext, and the product's
causal IDs as observerContext. A labelled observer-context link connects
them; the IDs are never falsely presented as identical. Explicit source
facts retain institutionProvided origin; automatic HTTP/lifecycle facts use
sdkAutomatic, and real OTEL spans use nativeOtel.
The independent x-sigillum-context v2 carrier is scope checked and MAC verified.
Its frozen scope token is independently signature verified; possessing the
institution's propagation MAC key does not authorize a different service set.
It always includes an optional-reference object. If references would exceed
the 16 KiB wire limit, only their propagation is omitted; all original local
facts are retained, the signed causal core continues, and
referencePropagationPartial is reported. Active references are not changed.
It is observation metadata, never business authorization. SDK-free components
are not inspected; if they pass the carrier, connected endpoints keep the
actual relationship. Parallel calls remain separate children. Missing context
is never reconstructed by time, content, person or session equality.
The worker batches only already-ready records from the same execution and frozen profile, at most 16 records and 8 MiB without waiting for a threshold. A larger valid single record travels alone within the 32 MiB transport bound. It retries identical packet bytes and delivery ID. A failed packet stays in the same byte/slot reservation and waits its backoff in a fair bounded retry rotation; it does not prevent other executions from reaching durable custody. No retry creates another copy of the pending packet or releases its credit before a verified receipt. Only an actual root-span record from its own root service repeats the immutable root admission. Child-span batches keep an empty admission list, including children in that same process; a shared-service continuation never invents a foreign admission. The memory copy is released only after the exact scoped record/package/delivery bindings and pinned P-256 custody receipt verify. This receipt proves durable product custody, not final central publication. RAM before that receipt is not durable and can be lost on process failure. The product-owned transfer worker performs durable encrypted storage and subsequent retries. The observer delivery transport uses at most one concurrent IPv4 and one IPv6 connection attempt, cancelling and disposing the losing connection. The original hostname, TLS server name and strict installation CA checks remain intact. This configuration never changes the application's HTTP client.
