Invarix.Gate.Evidence
1.0.0-rc.1
dotnet add package Invarix.Gate.Evidence --version 1.0.0-rc.1
NuGet\Install-Package Invarix.Gate.Evidence -Version 1.0.0-rc.1
<PackageReference Include="Invarix.Gate.Evidence" Version="1.0.0-rc.1" />
<PackageVersion Include="Invarix.Gate.Evidence" Version="1.0.0-rc.1" />
<PackageReference Include="Invarix.Gate.Evidence" />
paket add Invarix.Gate.Evidence --version 1.0.0-rc.1
#r "nuget: Invarix.Gate.Evidence, 1.0.0-rc.1"
#:package Invarix.Gate.Evidence@1.0.0-rc.1
#addin nuget:?package=Invarix.Gate.Evidence&version=1.0.0-rc.1&prerelease
#tool nuget:?package=Invarix.Gate.Evidence&version=1.0.0-rc.1&prerelease
Invarix.Gate.Evidence
Writes Invarix.Gate tool-call verdicts into an Invarix.Guard.Evidence decision log.
Gate decides what an agent is allowed to do. Evidence keeps an append-only record of AI decisions in CloudEvents 1.0 form. This package is the bridge: every verdict Gate renders becomes a decision record, without putting evidence I/O on the tool-call path.
Requires a commercial Gate license.
Setup
services.AddInvarixGate(gate => gate
.Mode(GateMode.Enforce)
.UseDefaultPack());
services.AddInvarixGuardEvidence(evidence =>
{
evidence.AISystemId = "support-agent";
evidence.AISystemVersion = "3.1.0";
evidence.ModelId = "gpt-4o";
evidence.ModelVersion = "2024-11-20";
})
.UseJsonlStore("/var/evidence/decisions.jsonl");
services.AddInvarixGateEvidence();
Order matters. AddInvarixGateEvidence throws at startup if either of the other two calls is
missing, and names the one to add.
The AI-system and model identity come from AddInvarixGuardEvidence and are not repeated in
the bridge's options. One declaration, validated once, so the two cannot drift.
modelId on a Gate record is the agent's model, not Gate. Gate runs no model; its decision
path is pattern matching. The field names the model whose tool call was judged.
The mapping
| Gate | Decision record |
|---|---|
| every evaluated call | one record of type dk.invarix.gate.toolcall.v1 |
| a resolved approval | a second record of type dk.invarix.gate.approval.v1 |
| tool name | subject |
ToolCallRequest.CanonicalArgumentsJson |
inputHashSha256, the exact SHA-256 |
| no output (Gate decides before the tool runs) | outputHashSha256 = SHA-256 of the empty string |
each RuleFinding |
a detectorsFired entry: name = rule id, redactedPreview = redacted match |
finding action Allow / Warn / Escalate,Deny,Terminate |
detector verdict pass / warn / block |
| nothing fired | outcome: allowed |
| a warning | outcome: allowedWithWarnings |
| observe mode, would have blocked | outcome: allowedWithWarnings |
| enforce mode, denied or terminated | outcome: blocked |
| escalated, awaiting a human | outcome: blocked, oversight manualReview |
| approval granted | outcome: overridden, oversight override |
| approval refused | outcome: blocked, oversight reverse |
| operator stop-run | outcome: blocked, oversight stopButton |
Only the exact argument digest is recorded. Gate's loop hash is deliberately lossy: it drops volatile arguments and folds names to lowercase, so it does not commit to what actually ran. It never appears in evidence.
Escalations are resolved in GateEngine.ResolveApprovalAsync. The overload taking the
call, which the shipped adapters use, notifies approval observers, this bridge among
them, so the resolution is recorded with no extra wiring. A host that resolves approvals
through its own machinery calls GateEvidenceBridge.OnApprovalResolved(resolved, call, decision) with the verdict it got back. Either way, an ApprovalDecision from a broker
records the operator structurally; without one the outcome is recorded with no operator,
which is the truthful reading of a timeout or a broken broker.
The two logs carry different things
Gate's own verdict log (docs/VERDICT-LOG.md in the Gate package) records tool arguments' hash
alongside spend figures, ledger and loop state, coverage tallies, and the policy pack version.
None of that reaches the evidence store.
DecisionData is sealed, has a fixed field set, no extension slot, and a wire format frozen by
golden-vector tests. There is no honest place to put those values, so the bridge does not put
them anywhere: nothing is encoded into a detector name, packed into a redacted preview, or
appended to the subject. A record carrying a value in a field that does not mean it is not
evidence, it is a decoding puzzle whose answer nobody wrote down.
The same limit costs the record the agent id, the run id, and the call id. If you need evidence
searchable by run, set CorrelateByRunId = true and the run id becomes the record's
correlationId instead of the ambient trace id.
An optional extensions object on DecisionData is a plausible future change to Evidence. It is
not something to work around now.
Latency and loss
IGateVerdictObserver.OnVerdict is synchronous and runs while the agent waits. Mapping happens
there, because it is pure CPU work and has to see the verdict as rendered. The write is queued
and drained by a background task, so no tool call waits on a disk.
The queue is bounded (QueueCapacity, default 4096). When it fills, the oldest record is
evicted: blocking would put evidence latency back on the tool-call path, and dropping the newest
would keep the start of an incident and lose the part where it went wrong. Every eviction,
failed write, and record abandoned at shutdown increments DroppedRecordCount, and the first
one logs a warning. Non-zero means the log is incomplete for that process.
A write failure is logged and swallowed. A broken evidence store must not break a running agent.
Disposal stops intake and drains the queue, bounded by DrainTimeout (default 5 seconds), so a
clean shutdown keeps the evidence it already produced.
Tamper evidence
Records written through this bridge are tamper-evident only when the host enables sealing with
its own keys. Invarix.Guard.Evidence ships Merkle batch commitments and Ed25519 signing as an
opt-in layer that you call with a key you control; AddInvarixGuardEvidence alone does not turn
it on. Without sealing, a JSONL evidence file is plain text that anyone with write access can
edit.
Even with sealing, the claim is bounded. In-process evidence proves that a record was not altered after it was written. It cannot prove that a compromised process wrote honest records in the first place: something that has already taken over the host can sign whatever it likes with the key that host holds. Sealing raises the cost of quiet retroactive edits. It is not proof of what happened.
License
Commercial. Contact sales@invarix.dk.
| 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 is compatible. 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. |
-
net10.0
- Invarix.Gate (>= 1.0.0-rc.1)
- Invarix.Guard.Evidence (>= 1.0.0-rc.1)
-
net8.0
- Invarix.Gate (>= 1.0.0-rc.1)
- Invarix.Guard.Evidence (>= 1.0.0-rc.1)
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 |
|---|---|---|
| 1.0.0-rc.1 | 64 | 8/6/2026 |