Skip to content

feat(cas): emit audit events on artifact uploads and downloads#3189

Merged
migmartri merged 4 commits into
mainfrom
feat/cas-audit-events
Jun 11, 2026
Merged

feat(cas): emit audit events on artifact uploads and downloads#3189
migmartri merged 4 commits into
mainfrom
feat/cas-audit-events

Conversation

@migmartri

@migmartri migmartri commented Jun 11, 2026

Copy link
Copy Markdown
Member

Closes #3187

The Artifact CAS now publishes audit events for artifact uploads, downloads and deduplicated (skipped) uploads to the existing chainloop-audit NATS JetStream stream, so a consumer of the stream can compute per-organization upload volume, download volume and deduplication savings.

Implementation overview

  • Two new event types in the existing controlplane events package, following the same auditor.LogEntry format as the rest of the audit events: CASArtifactUploaded and CASArtifactDownloaded. Events carry the organization, digest, size in bytes, filename and backend type, and are published to audit.casartifact.* subjects.
  • The events are emitted by the CAS data plane itself (gRPC bytestream write/read and the HTTP download endpoint), since the CAS is the only component that observes the deduplication decision and the actual byte transfers. Emission happens only after the operation's success point: uploads stored, downloads checksum-verified.
  • A new thin AuditDispatcher in the CAS resolves the organization from the request JWT claims and publishes through the shared auditor machinery.
  • New optional nats_server section in the CAS configuration, exposed in the Helm chart as cas.nats.* values.

Major design decisions

  • Deduplicated uploads are not a separate event type: they are CASArtifactUploaded events with a skipped: true flag (no bytes were transferred or stored). Since the stored size is not known at the dedup point, it is resolved with a best-effort Describe() call, falling back to 0; consumers can join on the digest with a prior non-skipped upload.
  • Internal control plane traffic emits no events: a new SourceInternal JWT claim is set only when the control plane mints tokens for its own CAS client (attestation storage, policy material reads). The boolean zero value means client traffic, so the client-facing token issuance paths needed no changes, and the suppression happens at emission so the stream needs no consumer-side filtering.
  • The control plane remains the single owner of the stream configuration: the CAS publisher runs in a new publish-only mode (NewPublishOnlyAuditLogPublisher), so it can never create or reconfigure the chainloop-audit stream (e.g. accidentally downgrade its replica count).
  • Events are best-effort and NATS stays optional: when nats_server is unset the behavior is unchanged; publish failures are logged and never fail or slow down an upload/download.
  • SYSTEM actor, no target ID: CAS JWTs carry only the organization, so events require no actor; artifacts are identified by their digest in the event payload. Actor attribution (user/API token identity in CAS tokens) is left as a possible follow-up.

Consumer math

Per organization, over a time window on the stream: uploaded bytes = sum of size_bytes where skipped == false; downloaded bytes = sum of CASArtifactDownloaded sizes; dedup savings = sum of size_bytes where skipped == true.

AI disclosure

This contribution was developed with AI assistance (Claude Code).

🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri

@chainloop-platform

chainloop-platform Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

AI Session Analysis

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🟢 88% 1 ✅ 0 93% AI / 7% Human 26 +1191 / -95 3h38m12s

🟢 88% — 93% AI — ✅ All policies passing

Jun 11, 2026 08:55 UTC · 3h38m12s · $44.17 · 219.1k in / 502.0k out · claude-code 2.1.173 (claude-fable-5)

View session details ↗

Change Summary

  • Adds CAS upload/download audit events, including deduplicated uploads via a skipped flag.
  • Threads internal-traffic claims, publish-only NATS wiring, Helm config, and PR metadata through the rollout.
  • Expands tests and follow-up fixes for wire generation, CI, rebase, and cleanup work.

AI Session Overall Score

🟢 88% — Well-scoped, well-verified implementation with calm user steering and no red flags.

AI Session Analysis Breakdown

🟢 91% · verification

🟢 Affected tests were added and rerun repeatedly across the touched packages. · High Impact

🟡 Validation was strong but remained test-runner driven rather than user-confirmed. · Low Severity

🟢 90% · alignment

No notes.

🟢 90% · user-trust-signal

🟢 The user kept delegating follow-up PR work without showing frustration. · Medium Impact

🟢 88% · context-and-planning

🟢 The prior spec served as the implementation plan before coding began. · High Impact

🟢 84% · scope-discipline

No notes.

🟢 82% · solution-quality

🟡 Publish-only mode first landed as a variadic constructor that CI wire generation rejected. · Low Severity


File Attribution

██████████████████░░ 93% AI / 7% Human

Status Attribution File Lines
modified ai app/artifact-cas/internal/service/auditor_test.go +190 / -11
created ai app/controlplane/pkg/auditor/events/casartifact_test.go +169 / -0
created ai app/artifact-cas/internal/service/download_test.go +123 / -0
created ai app/controlplane/pkg/auditor/events/casartifact.go +118 / -0
modified ai app/controlplane/pkg/auditor/nats_test.go +87 / -10
modified ai app/artifact-cas/internal/service/auditor.go +88 / -1
modified ai app/controlplane/pkg/auditor/nats.go +55 / -32
modified ai app/artifact-cas/internal/service/bytestream_test.go +80 / -6
modified ai internal/robotaccount/cas/robotaccount_test.go +33 / -19
modified ai app/artifact-cas/cmd/wire.go +37 / -10
modified ai app/artifact-cas/internal/service/bytestream.go +39 / -1
modified ai internal/robotaccount/cas/robotaccount.go +21 / -1
created human app/controlplane/pkg/auditor/events/testdata/casartifacts/casartifact_upload_skipped.json +19 / -0
created human app/controlplane/pkg/auditor/events/testdata/casartifacts/casartifact_uploaded.json +19 / -0
created human app/controlplane/pkg/auditor/events/testdata/casartifacts/casartifact_downloaded.json +18 / -0
created human app/controlplane/pkg/auditor/events/testdata/casartifacts/casartifact_upload_skipped_unknown_size.json +18 / -0
modified ai app/artifact-cas/internal/conf/conf.proto +11 / -0
modified ai app/artifact-cas/internal/service/download.go +11 / -0
modified ai app/controlplane/pkg/biz/cascredentials.go +10 / -1
modified ai deployment/chainloop/values.yaml +11 / -0
modified human deployment/chainloop/templates/_helpers.tpl +9 / -0
modified ai app/artifact-cas/internal/service/service.go +8 / -0
modified ai deployment/chainloop/templates/cas/secret-config.yaml +7 / -0
modified ai app/controlplane/pkg/biz/casclient.go +4 / -2
modified ai deployment/chainloop/README.md +5 / -0

…and 1 more file(s).


Policies (4)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-0c60a3 -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-0c60a3 -
✅ Passed ai-config-no-secrets ai-coding-session-0c60a3 -
✅ Passed ai-config-mcp-servers-allowed ai-coding-session-0c60a3 -

Powered by Chainloop and Chainloop Trace

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 28 files

Re-trigger cubic

@migmartri migmartri requested a review from a team June 11, 2026 12:09
The Artifact CAS now publishes best-effort audit events to the
control-plane-owned NATS JetStream stream (chainloop-audit) so a
consumer can compute per-organization upload volume, download volume
and deduplication savings:

- New CASArtifactUploaded (with a skipped flag for deduplicated
  uploads) and CASArtifactDownloaded event types.
- New optional nats_server section in the CAS configuration; when
  unset, behavior is unchanged.
- The CAS publisher runs in a new publish-only mode
  (WithoutStreamManagement) so it never overrides the stream
  configuration owned by the control plane.
- New SourceInternal JWT claim flags the control plane's own CAS
  traffic, which emits no events to keep org usage numbers clean.
- Helm chart: new optional cas.nats values.

Fixes #3187

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
@migmartri migmartri force-pushed the feat/cas-audit-events branch from 6299c5e to c098da7 Compare June 11, 2026 12:16
Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
@migmartri migmartri enabled auto-merge (squash) June 11, 2026 12:32
…y constructor

wire cannot resolve variadic providers: the CI generate check failed
because NewAuditLogPublisher's new variadic options parameter made wire
demand a []auditor.PublisherOption provider in the controlplane and
testhelpers injectors. Restore the original NewAuditLogPublisher
signature and expose the publish-only mode as a separate wire-friendly
NewPublishOnlyAuditLogPublisher constructor used directly by the CAS.

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 0c60a332-e0f1-4c2b-94ae-533467e52f5c
@migmartri migmartri force-pushed the feat/cas-audit-events branch from 45eac6e to 0725554 Compare June 11, 2026 12:33
@migmartri migmartri merged commit cd47996 into main Jun 11, 2026
15 checks passed
@migmartri migmartri deleted the feat/cas-audit-events branch June 11, 2026 12:56
log *log.Helper
}

func NewAuditDispatcher(publisher *auditor.AuditLogPublisher, logger log.Logger) *AuditDispatcher {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot it reuse the dispatcher from controlplane? perhaps moving it out of internal?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will run a follow up, thanks

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call @jiparis. I extracted the shared generate → publish → error-reporting flow into a new auditor.Dispatcher in pkg/auditor (importable, not internal), and both biz.AuditorUseCase and this CAS AuditDispatcher now delegate to it — each keeping only its own actor/org resolution. Reusing biz.AuditorUseCase directly wasn't viable since it resolves the actor from the control-plane request context and importing biz would pull the whole control-plane dependency graph into the CAS, so the shared seam lives at the lower-level dispatcher instead.

Follow-up: #3191

🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audit events for CAS artifact uploads, downloads and deduplication savings

3 participants