feat(dotnet): content safety adapter and managed stream accounting - #38
Closed
liamcrumm wants to merge 3 commits into
Closed
feat(dotnet): content safety adapter and managed stream accounting#38liamcrumm wants to merge 3 commits into
liamcrumm wants to merge 3 commits into
Conversation
Adds AgentControlSpec.ContentSafety, which maps content safety observations onto release decisions, and the managed StreamSession the adapter needs. BlockingCriterion.Matches takes a TaskObservation of severity, detected or score and decides whether a task blocks. ContentSafetySession carries the per track offsets and watermarks and collapses the per task actions into one outcome per segment. ContentSafetySourceMapping reproduces the front door's split between the raw source, which decides the track counting offsets, and the resolved source, which decides which tasks apply. The two disagree and the adapter preserves that rather than correcting it, because an offset the front door does not agree with releases the wrong text. The accounting is a second implementation of section 18.1, since the C ABI exposes no streaming entry points. It was written in July against the pre review engine and is refactored here onto the shipped semantics. A transform is terminal and reports StreamEndReason.Rewritten rather than clearing its span, the transform guard bounds on any released rune rather than the span start, SafeOffset returns null once the session has ended, resume offsets are per track, an empty task set means the track is unmediated, payload after the payload stream closes fails the session, and the decision mapping collapses to the three agent-hooks decisions. The trace exporter is ported alongside so the differential replays traces generated by the shipped engine. It caught three of those divergences on first run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Liam Crumm <liamcrumm@gmail.com>
Pin the transform bound. The refactor narrowed the guard from the span start to any released rune, but the existing test transformed a span overlapping released text, which fails under both readings. A span starting exactly at the watermark distinguishes them, and nothing covered it, so the fail-open could have been reintroduced silently. Correct three doc comments that described the withdrawn design: that a transform clears its span rather than ending the session, that StreamCompletion.Transformed tracks a cleared span rather than a terminal rewrite, and that RecordVerdict maps warn and escalate, which agent-hooks no longer defines. Mark the ContentSafety project unpackable. It carried full package metadata while the release workflow packs only the core project, so the file claimed an intent that nothing performed. Publishing it would fix the package id here while its home is still open. Cover the new csproj in the version gate, which read only the core project and would not have caught the new one drifting. Restore the solution file to a minimal edit. Adding the project through the CLI also wrote x64 and x86 configurations for every existing project, which is 51 lines of unrelated churn. Note in the rego library that it and the C# answer to the same committed oracle and that only the C# is checked against it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Liam Crumm <liamcrumm@gmail.com>
The exporter builds the expected trace with LF endings and compares it byte for byte against the committed file, and the .NET differential replays the same bytes. On a Windows checkout autocrlf rewrote the file to CRLF, so the comparison failed for a reason unrelated to the behavior under test. Mark it -text, as the SSE conformance vectors already are for the same reason. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Liam Crumm <liamcrumm@gmail.com>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
AgentControlSpec.ContentSafety, an adapter that maps content safety observations onto release decisions, and the managedStreamSessionit depends on. Both were written in July against the pre review engine and are refactored here onto the semantics that shipped in 0.4.0-alpha.2.What the adapter does
BlockingCriterion.Matchestakes aTaskObservationof severity, detected or score and decides whether that task blocks. Criteria are severity threshold, risk level bucket, detected flag and score threshold.ContentSafetySessionowns the offsets and watermarks. A host reports a payload, then reports a segment with one observation per task, and gets back aContentSafetyOutcomesaying whether the segment may be released, which action the session collapsed to, and which tasks matched. Actions carry through as flags so a block and a filter do not become the same answer.ContentSafetySourceMappingsplits two questions the front door answers differently. The raw source as it arrived decides which track counts the offsets. The resolved source, after context folds into model generated text and concatenated history folds into the request, decides which tasks apply. A payload carrying concatenated history evaluates as a request but has its runes counted on the completion track. The adapter preserves that disagreement rather than correcting it, because an offset the front door does not agree with releases the wrong text.State
The accounting is a second implementation of specification section 18.1. The C ABI exposes no streaming entry points, so the .NET SDK cannot bind the Rust engine for this and reimplements it. Nothing structural keeps the two in step, so
StreamSessionDifferentialreplays traces the engine exports and fails on any divergence in either direction.That replay is doing real work. The July implementation carried the pre review design and the differential caught three divergences against the shipped engine on first run, all of which released text no task had evaluated. Refactored here:
transformStreamEndReason.RewrittenConfirmed > span.StartSafeOffsetafter a denynullThe offset a track reached stays readable through
Watermarkfor an audit record.Tests
40 .NET tests pass, 8 of them ported here. 115 rego tests pass.
cargo test --workspace --all-features --locked,cargo clippy --workspace --all-features --locked -- -D warnings,cargo fmt --all --checkandscripts/check-version-consistency.pyare clean.The .NET tests need the native library on the loader path, which the NuGet package does not carry. Build it with
cargo build -p agent-control-spec-ffi --releaseand run withLD_LIBRARY_PATH=$PWD/target/release. Without it the pre existing interceptor and manifest tests fail to load, which is unrelated to this change.Known gaps
The adapter's vocabulary is front door wire vocabulary.
ContentSafetySourcemirrors the proto and the source mapping reproduces one service's payload processor. That argues for the adapter living with the consumer rather than in this repo, and it is worth deciding before this merges. The project is marked unpackable until that is settled, so nothing publishes under a package id that a later move would break.The managed accounting is a maintenance liability for as long as it exists. Streaming entry points in the C ABI would remove it and the drift class with it.
examples/streaming_frontdoor/, a localhost demo, is not included. It depends on an Azure Content Safety credential.