feat(server): AuditSink Protocol + LoggingAuditSink/SlackAlertSink#362
Merged
feat(server): AuditSink Protocol + LoggingAuditSink/SlackAlertSink#362
Conversation
…ference impls Round-2 P1 observability seam paralleling DeliveryLogSink. AuditEvent (frozen Pydantic) records skill dispatches; AuditSink Protocol is the adopter extension point. make_audit_middleware() composes one or more sinks into a SkillMiddleware with per-sink timeout + log-and-swallow so a wedged sink can't stall dispatch. Reference impls: - LoggingAuditSink: stdlib logging at INFO, structured JSON via Pydantic model_dump_json - SlackAlertSink: SecOps alerting on a sensitive-ops subset, routed through build_async_ip_pinned_transport with trust_env=False; details egress gated by an explicit allowed_fields allowlist (default frozenset() — no details reach Slack); webhook URL redacted in __repr__ Field naming aligns with ToolContext (caller_identity, tenant_id, request_id) so middleware copies fields without translation. Module docstring explicitly names SOX, GDPR Art. 30, and IAB TCF as regimes this best-effort design does NOT satisfy — adopters needing those implement AuditSink against a transactional store directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Closes #351.
Summary
Round-2 P1 observability seam paralleling
DeliveryLogSink. New modulesrc/adcp/audit_sink.py:AuditEvent— frozen Pydantic model. Field naming aligns withToolContext(caller_identity,tenant_id,request_id) so middleware copies fields without translation.extra="forbid"catches typo'd field names at construction.AuditSink— runtime-checkable Protocol. Adopter extension point.LoggingAuditSink— stdlibloggingat INFO, structured JSON viamodel_dump_json().SlackAlertSink— SecOps alerting on a sensitive-ops subset (NOT audit-of-record). Routes throughbuild_async_ip_pinned_transportwithtrust_env=False;detailsegress gated by an explicitallowed_fieldsallowlist (defaultfrozenset()— no details reach Slack); webhook URL redacted in__repr__.make_audit_middleware()— composes one or more sinks into aSkillMiddleware. Per-sinkasyncio.wait_for+ log-and-swallow so a wedged sink cannot stall dispatch. Records on success AND on exception (re-raises).Design decisions
The triage on #351 raised four open questions. Resolutions:
caller_identity/tenant_idonAuditEvent, notprincipal_id. MatchesToolContext.SlackAlertSinkusesbuild_async_ip_pinned_transport+trust_env=False, likeWebhookSender.detailsfiltering — explicitallowed_fields: frozenset[str]allowlist, defaultfrozenset()(emit nothing fromdetailsto Slack unless opted in).DeliveryLogSink. Module docstring explicitly names SOX, GDPR Art. 30, and IAB TCF as regimes this design does not satisfy, with the "implementAuditSinkagainst a transactional store directly" escape hatch made impossible to miss.Expert review applied
After the initial draft, ran two parallel expert reviews (Python ecosystem patterns + ad-tech observability standards). Resulting changes:
AuditEvent: dataclass → frozen Pydantic for SDK consistency, freemodel_dump_json(), and datetime validation at construction.SlackAuditSink→SlackAlertSink: Slack is universally an alerting channel in ad-tech ops, never audit-of-record. Rename prevents the misuse pattern of wiring it as the only sink and assuming compliance is satisfied.Deferred as follow-ups:
OTelAuditSinkreference impl (own PR — adds dep tree),AuditEvent.to_cloudevent()projector (YAGNI until a real adopter asks).Test plan
test_webhook_supervisor.pystill passes (no regression)httpx.MockTransport), default allowlist drops all details, allowlist filtering, non-2xx surfaces to operator log, exception-path audit, sink timeout isolation, sink raise isolation, multi-sink continuation after one bad sink, empty-sinks no-op, unauthenticated-request handling, frozen-Pydantic assignment raises,extra="forbid"rejects unknown fields, native JSON serialization🤖 Generated with Claude Code