Skip to content

feat(ocsf): OCSF-to-proto generator (ocsf-protogen) [AI-1408]#174

Merged
sago2k8 merged 26 commits into
mainfrom
sj/ocsf-protogen
Jul 7, 2026
Merged

feat(ocsf): OCSF-to-proto generator (ocsf-protogen) [AI-1408]#174
sago2k8 merged 26 commits into
mainfrom
sj/ocsf-protogen

Conversation

@sago2k8

@sago2k8 sago2k8 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Epic: AI-1408

Summary

Adds ocsf, a new module that turns the compiled OCSF (Open Cybersecurity Schema Framework) security-event schema into wire-stable proto3, plus an OCSF-JSON exporter and a generator CLI. This is the foundation for emitting Redpanda authorization and audit activity as OCSF events; consumers pin an OCSF version and generate only the classes they need.

The OCSF project ships no proto binding, and the one community generator (valllabh/ocsf-tool) renumbers fields on every schema change (wire-unstable), drops requirement levels, and doesn't compose profiles. This module fixes those.

What's here

  • internal/ocsf/schema — loads the OCSF 1.8.0 export/v2 compiled schema into typed Go.
  • internal/ocsf/gen — OCSF→proto type/enum mapping and deterministic proto3 emission (nested enums, protovalidate, conditional imports).
  • internal/ocsf/tagmap — the wire-stability core: an append-only (message, attribute) → tag map with a compatibility check.
  • internal/ocsf/exporter — marshals a generated proto message to schema-valid OCSF JSON.
  • cmd/ocsf-protogen — the CLI (--check, --compat-check).
  • internal/ocsf/conformance — end-to-end acceptance tests.

Highlights

  • Wire-stable field numbers. OCSF has none and proto needs them permanently. The tag map is append-only, retires tags to a reserved list (never reused), and skips the 19000-19999 proto range. ocsf-protogen --compat-check diffs the committed tag map against the base branch in CI and fails on any incompatible change.
  • Correct OCSF-JSON export. Stock protojson is invalid OCSF on three counts (enum names instead of integers, 64-bit ints as quoted strings, camelCase keys). The reflection-based exporter always emits integer enums, numeric int64 (exact beyond 2^53), and snake_case keys, with a divergence test that proves it differs from protojson on exactly those points.
  • Live conformance. The acceptance test compiles the generated 6003/3004 protos to Go, round-trips events through proto binary, and validates their OCSF-JSON export against the live OCSF server (POST /{version}/api/v2/validate), gating on error_count.

Notes

  • Schema source is GET https://schema.ocsf.io/{version}/export/v2/schema (the legacy /export/schema returns 400 for 1.8.0). Pinned to OCSF 1.8.0.
  • The conformance test needs outbound HTTPS to schema.ocsf.io; it runs in the ocsf-checks CI job and skips locally only on genuine connectivity failure (a 200 with errors is a failure, never a skip).
  • Emitted events must declare metadata.profiles for profile-gated attributes (cloud, disposition_id, authorizations); the conformance events do this.

Test

go test ./... green across all six packages; go vet clean; buf lint/buf build pass on the generated proto; each commit builds independently.

🤖 Generated with Claude Code

@secpanda

secpanda commented Jul 1, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@sago2k8 sago2k8 changed the title feat(ocsf): OCSF-to-proto generator (ocsf-protogen) feat(ocsf): OCSF-to-proto generator (ocsf-protogen) [AI-1408] Jul 1, 2026
@sago2k8 sago2k8 force-pushed the sj/ocsf-protogen branch from 7ddfd0b to 1295bf0 Compare July 1, 2026 11:08
sago2k8 and others added 9 commits July 1, 2026 15:08
Loads the OCSF 1.8.0 export/v2 compiled schema (classes, objects,
dictionary, types, enums) into typed Go. Fixture fetched from
schema.ocsf.io/1.8.0/export/v2/schema. Errors on a missing schema version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OCSF *_t types resolve via their base chain (timestamp_t->int64,
float_t->double, json_t->google.protobuf.Value, string-derived->string).
Enums reuse OCSF 0=Unknown as the proto zero value, prefix value names to
avoid collisions, guard against int32 overflow, and fall back to a string
field for string-keyed enums. Dispatch sentinels are grouped constants.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Append-only (message,attribute)->tag map: lowest-free allocation skipping
the 19000-19999 proto-reserved range, retire-to-reserved so tags are never
reused, load-time validation (positive tags, no dupes, no reserved-range
tags), and CheckCompat rejecting changed, dropped-unreserved, reused-reserved,
or un-reserved tags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deterministic proto3 emission: nested enums for int-keyed enums, stable
field numbers via the tag map, protovalidate for required fields and
at_least_one/just_one constraints, conditional well-known imports, and
reporting of objects emitted as stubs. Unresolvable non-object types error
rather than silently degrading. The proto package is the OCSF major version
(ocsf.v1); minor bumps are additive within it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflection-based marshaller producing schema-valid OCSF JSON: enums always
emit as integers (singular, nested, repeated, map), 64-bit ints as JSON
numbers (exact beyond 2^53), snake_case keys, and unset fields omitted.
Fixes the three ways stock protojson diverges from OCSF, proven by a
divergence test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
generate mode emits the proto and saves the tag map; --check gates on
output drift and newly-stubbed objects; --compat-check runs CheckCompat
between the base-ref and current field-number maps to enforce wire
stability. Includes the committed baseline proto and field-numbers.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
End-to-end acceptance test: the generated 6003/3004 protos compile to Go,
populated events round-trip through proto binary, and their OCSF-JSON export
is validated live via POST /{version}/api/v2/validate (gating on error_count,
checking HTTP status, skipping only on genuine connectivity failure). Events
declare metadata.profiles for profile-gated attributes. genpb regenerates
from the module root into conformance/genpb.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the ocsf module to the test and lint matrices and an ocsf-checks job
running buf lint/build, ocsf-protogen --check, the git-based --compat-check
against the base ref, and the live OCSF conformance test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the ocsf module's README and BSL LICENSE, a CODEOWNERS entry
(@redpanda-data/team-ai), and a .licenseupdater.yaml entry, satisfying the
repo Project Structure Check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sago2k8 sago2k8 force-pushed the sj/ocsf-protogen branch from 66728ec to 195d28e Compare July 1, 2026 13:10
sago2k8 and others added 2 commits July 1, 2026 16:06
Emit now returns []GeneratedFile (one .proto per selected class plus a shared
ocsf/v<N>/objects.proto) instead of a single monolithic file. --out becomes a
module-root directory; --check compares the full generated file set against the
committed tree (add/remove/change). Field numbers are keyed by message+attribute
and are byte-identical to the prior baseline. Regenerated golden tree, cmd
testdata tree, and genpb bindings; dropped the now-unneeded PACKAGE_DIRECTORY_MATCH
lint waiver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generic ocsf.v<N> protos carry no Schema-Registry annotation, so there
is no schema text a consumer can register directly. Add EmitSRSchemas, which
emits one self-contained <class>.sr.proto per class for explicit SR
registration by consumers.

Each file inlines the class message plus its full transitive object closure:
the class message is emitted first so it lands at Confluent message-index 0
(what decoders rely on). All buf.validate output is stripped (no
per-field required annotations, no message-level CEL blocks) and the objects
file is never imported. Field numbers reuse the wire tagmap via the idempotent
Assign, so tags are identical to the main Emit output.

emitMessage gains an emitOptions{omitValidate} parameter threaded from the SR
path; the main callers pass the default (validate on), leaving existing golden
output unchanged. The CLI gains --sr-schema-out <dir>, integrated with --check
for drift detection. The sr/ golden dir is excluded from the buf module since
each .sr.proto is a standalone artifact, not part of the module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

sago2k8 and others added 8 commits July 3, 2026 19:11
A single audit-log topic needs ONE message covering every selected class,
not one message per class. MergeClasses unions the attribute maps of N
classes into a single flat definition, the schema-level half of the merged
single-event layout.

Merge rules per attribute name:

- Type, object type, and is_array must agree across every declaring class;
  a mismatch is a hard generation error since the proto field type would
  otherwise depend on class selection.
- Integer-keyed enums union by value. When the same numeric value carries
  different captions in two classes (a class-scoped enum), the attribute is
  demoted to its plain scalar type instead of failing: OCSF defines such
  semantics via the (class_uid, value) pair, which the merged message keeps.
  Enum-presence and key-kind mismatches demote too.
- activity_id is demoted unconditionally. Its values genuinely collide
  across OCSF classes (1 = Create in api_activity, Logon in authentication,
  Assign Privileges in group_management), and demoting it later would break
  generated-code consumers, so it is int32 from day one; readable per-class
  names live in the disjoint-by-construction TypeUid union.
- Requirement merges to "required" only when required in EVERY class; a
  blanket annotation would wrongly reject events of classes that do not use
  the attribute. Per-class requiredness becomes gated CEL in the emitter.

The returned Merged also carries per-attribute owner classes, feeding the
emitter's class-ownership validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EmitMerged turns a MergeClasses result into the single-topic layout: one
flat message (e.g. AuditEvent) holding the union of all selected classes'
attributes, next to the same shared objects.proto the per-class layout uses
(byte-identical: same closure, same tagmap keys). EmitMergedSRSchema emits
the one self-contained <name>.sr.proto for the topic, merged message first
so it stays at Confluent message-index 0.

Class awareness that the per-class layout got from separate message types
is regenerated as protovalidate message-level CEL:

- type_uid == class_uid * 100 + activity_id (the OCSF invariant that makes
  the demoted int32 activity_id safe),
- ownership gates: attributes owned by a strict subset of classes may only
  be set when class_uid matches an owner,
- per-class conditional requiredness for attributes required by one class
  but not all (repeated attributes excluded, matching the per-class rule),
- per-class at_least_one/just_one constraints, gated on class_uid.

emitMessage gains fieldComments (demoted class-scoped enums get an
explanatory comment) and extraCEL (pre-rendered option blocks), both
suppressed by omitValidate on the SR path; the constraint helpers are
refactored to expose expression-level variants for the gated forms.

Field numbers are a fresh append-only lineage keyed by the merged message
name; a test proves adding a class to the selection never moves an existing
tag. Golden output for api_activity + entity_management is committed under
testdata/golden-merged and passes buf lint/build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--merged-message <Name> (e.g. AuditEvent) emits the merged single-event
layout IN ADDITION to the per-class files, into the same output tree and
sharing the same tagmap. Both paths emit an identical objects.proto; the
duplicate is dropped after an equality check so any divergence surfaces as
an error instead of a silent overwrite. With --sr-schema-out, the merged
<snake_name>.sr.proto lands next to the per-class SR schemas. --check
covers the merged files with the same drift and stray-file detection.

The committed testdata baseline is regenerated with the flag: it gains
ocsf/v1/audit_event.proto, and field-numbers.json gains the AuditEvent
lineage (purely additive; --compat-check against the previous map passes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pass --merged-message AuditEvent to the baseline-drift check so CI verifies
the committed audit_event.proto and the AuditEvent tagmap lineage exactly
like the per-class files (the existing --compat-check already covers the
new lineage since the tagmap is shared). Add buf lint/build over the
golden-merged tree used by the gen tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The exporter only went one way; audit events also need to come back from
OCSF JSON (topic consumers, Postgres JSONB columns, events produced by
other OCSF tools). FromOCSFJSON is the exact inverse of ToOCSFJSON:

- snake_case keys resolve by proto field name; enums decode from OCSF
  integer values, keeping unknown numbers verbatim (proto3 enums are open,
  so newer-revision events survive),
- 64-bit integers decode from unquoted JSON numbers without float64
  precision loss, plus quoted decimal strings for protojson interop;
  fractional or exponent forms into integer fields are rejected rather
  than truncated,
- google.protobuf.Value/Struct/ListValue fields take their natural JSON;
  for Value fields JSON null decodes to a set NullValue (protojson
  semantics), preserving the present-with-null vs absent distinction,
- JSON null on other fields leaves them unset; null is rejected as a
  message body or array element (encoding/json would otherwise no-op and
  silently zero the value),
- unknown keys land in the message's unmapped field (merged with any
  explicit unmapped object), mirroring OCSF's forward-compat model; a
  message without one errors, naming the keys.

Conformance now proves both directions on the real generated classes:
proto -> JSON -> proto is proto.Equal-lossless, JSON -> proto -> JSON is
byte-identical, and spliced unknown keys survive via unmapped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generate Go bindings for the merged layout (buf.gen.merged.yaml over
golden-merged; objects.proto maps to the same Go package as the per-class
bindings and is byte-identical, so only audit_event.pb.go is new) and prove
the four properties the single-topic design rests on:

1. Wire round-trip on the AuditEvent bindings for events of both classes.
2. JSON equivalence: an AuditEvent exports OCSF JSON deeply equal to the
   per-class message carrying the same event, and per-class JSON decodes
   into the equivalent AuditEvent — one topic loses nothing.
3. JSON round-trip both ways (lossless / byte-identical).
4. protovalidate (new test dependency buf.build/go/protovalidate) enforces
   the generated class-aware CEL: valid events of both classes pass, and
   type_uid inconsistency, foreign-class fields, and missing per-class
   required fields are each rejected.

The merged events also validate CLEAN against the live OCSF server
(schema.ocsf.io api/v2/validate), same skip-on-connectivity contract as
the per-class conformance test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found by fuzzing the round-trip: a proto string field carrying invalid
UTF-8 exported "successfully" with the invalid bytes silently replaced by
U+FFFD by encoding/json — data corruption reported as success, and an
asymmetric round-trip. proto3 requires string fields to be valid UTF-8
(proto.Marshal enforces the same), so ToOCSFJSON now rejects invalid UTF-8
in string fields and string map keys with an explicit error.

Regression tests land with the adversarial suite in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hostile-input coverage for ToOCSFJSON/FromOCSFJSON on the sample message
and on the real generated classes:

- malformed JSON (truncated, garbage, wrong top-level types, control
  characters in keys) must error, never panic;
- type confusion for every field kind, with the error naming the field;
- integer boundaries: int64/int32 min/max accepted exactly, off-by-one
  overflow rejected, fractional and exponent forms into integer fields
  rejected instead of truncated; NaN/Inf export rejected;
- hostile strings (unicode, JSON-injection attempts, 1 MiB payloads, null
  bytes) round-trip byte-exactly without smuggling fields past the class
  discriminator;
- 1000-deep nesting, duplicate keys, null array elements, and full
  truncation/mutation sweeps over real event JSON;
- seeded property smoke tests: hundreds of randomized events (SampleEvent
  and AuditEvent across both classes) must round-trip proto -> JSON ->
  proto losslessly and JSON -> proto -> JSON byte-identically;
- three fuzz targets whose seed corpus runs on every go test run;
  extended runs (>1M execs) are clean.

Fuzzing found and this suite pins down three real defects: the invalid
UTF-8 corruption fixed in the previous commit, JSON null dropping a set
google.protobuf.Value on import, and a nil-map panic when unknown keys
merge into an explicit null unmapped (crash input committed under
testdata/fuzz as a permanent regression corpus).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sago2k8 sago2k8 force-pushed the sj/ocsf-protogen branch from 4c3c6dd to 0e7279e Compare July 3, 2026 17:12
Describe the two generated layouts (per-class and merged single-event),
the activity_id demotion rationale, the FromOCSFJSON round-trip contract,
and the --merged-message / --sr-schema-out flags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sago2k8 sago2k8 force-pushed the sj/ocsf-protogen branch from 0e7279e to 84a9a2e Compare July 3, 2026 18:18
sago2k8 and others added 6 commits July 3, 2026 20:28
--merged-sr-subject <subject> emits the
(redpanda.api.common.v1.schema_registry) message option (and its import,
resolved from buf.build/redpandadata/common) on the merged event message.
Consumers that run protoc-gen-go-sr-normalize in their buf pass then get
the self-contained SR schema text and subject as generated Go constants
(<Name>SRSchema / <Name>SRSubject) directly from the emitted proto — no
hand-copied schema files. The --sr-schema-out path remains as the non-buf
route to the same schema text.

The annotation is a message option, so field numbers and the tagmap are
untouched; the committed baseline diff is the option block plus one
import. The .sr.proto emitters never carry the option: they must stay
self-contained, and a test now pins that. A conformance test reads the
extension back from the compiled AuditEvent descriptor exactly the way
protoc-gen-go-sr-normalize does, proving the annotation survives
generation end to end.

--merged-sr-subject requires --merged-message and is enforced. The
golden-merged and cmd testdata buf modules gain the redpandadata/common
dep; CI passes the new flag in the baseline-drift check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exporter/importer scenarios added:

- Unicode escapes: escaped JSON keys must resolve to proto fields,
  surrogate pairs round-trip, and a lone surrogate (which encoding/json
  replaces with U+FFFD) must decode to valid UTF-8 and re-export cleanly.
- Document-level oddities: BOM prefix, comments, and concatenated JSON
  documents are rejected; exotic-whitespace documents parse.
- More number formats: -0 into int64, float-valued enums (5.0) rejected
  rather than coerced, float64 overflow (1e309) errors instead of
  becoming +Inf, plus-sign/hex/NaN/Infinity literals rejected.
- Presence semantics pinned: implicit-presence zeros are accepted on
  import and canonicalized away on export; explicit-presence (optional)
  empty values keep byte-exact round-trips.
- 1000-deep array nesting, backslash added to the mutation sweep, a
  concurrent round-trip smoke test for the race detector, and new fuzz
  seeds for all of the above.

Conformance (real AuditEvent) scenarios added:

- unmapped stress: 500+ hostile unknown keys (empty, unicode, quoted,
  4 KiB names) all land in unmapped and stay idempotent.
- unmapped conflict semantics pinned: non-object unmapped plus unknown
  keys errors; top-level unknown keys win over duplicate entries in an
  explicit unmapped object; scalar unmapped without unknowns decodes.
- Importer-to-CEL loop: hostile-but-well-formed JSON (foreign-class
  field, type_uid mismatch, missing class-required field) must DECODE
  losslessly and then fail protovalidate on exactly the right generated
  rule, asserted via structured violation rule ids rather than error
  text. The empty event decodes, exports back to {}, and fails
  blanket-required validation.
- Property test variant splicing random unknown keys into random events:
  decode, unmapped population, and post-splice idempotence.

Suite passes under -race; fuzz targets re-run clean on the extended
seed corpus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from a dedicated test-coverage review, in priority order:

- Six proto field kinds (map, bytes, float, uint32/64, fixed, sint) had
  zero reachability anywhere in the module: OCSF's type system never
  produces them, but ToOCSFJSON/FromOCSFJSON are documented generic over
  proto.Message. The sample fixture now declares all of them, the
  randomized generator populates them, and dedicated cases cover base64
  round-trip and malformed input, uint64/fixed32 bounds and overflow,
  negative-into-unsigned, sint64 extremes, float32 quantization, hostile
  map keys, null/mistyped map values, duplicate-key semantics, and
  key-sorted deterministic map export.
- Tag stability is now proven across a real on-disk tagmap save/load
  cycle with a widened class set (the exact --compat-check workflow),
  including tagmap.CheckCompat on the persisted maps; the previous test
  only covered one in-memory tagmap.
- New genpb sync guard: field name→tag maps parsed from the committed
  golden protos must equal the compiled descriptors of the committed Go
  bindings, so editing a golden without regenerating genpb can no longer
  leave conformance tests passing against stale bindings.
- The string-keyed and mixed-kind branches of the enum union in
  MergeClasses (previously dead in tests) get conflict, union, and
  kind-mismatch cases.
- TestMergedProtovalidate now asserts structured violation rule ids
  instead of error-text substrings, matching the adversarial suite.
- SR subjects are validated against [A-Za-z0-9._-] at emit time instead
  of trusting Go %q escaping to be proto-literal-legal for exotic input,
  with hostile-subject rejection tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three low-severity findings from a dedicated correctness review (which
found no high-severity issues and confirmed enum-slice aliasing, CEL
semantics, importer reflection, UTF-8 rejection, objects.proto dedupe,
and wire stability all sound):

- decodeUint parsed via fmt.Sscanf %d, which accepts a fractional JSON
  number like 12.9 and silently truncates it to 12. Latent for OCSF
  types (no OCSF attribute maps to an unsigned kind) but FromOCSFJSON
  is generic over proto.Message; now strconv.ParseUint rejects it, with
  regression tests on the uint64 and fixed32 fixture fields.

- diffSRSchemas only checked generated-vs-committed one way: dropping a
  class from --classes left its stale <class>.sr.proto committed and CI
  green. Stray *.sr.proto files now fail --check, mirroring the main
  tree's stray-file detection.

- --merged-message naming a selected class's PascalCase message name
  (e.g. ApiActivity) would silently share one (message, attribute) tag
  lineage between two distinct messages. Now rejected with an explicit
  error via the exported gen.ClassMessageName.

Also bumps the regenerated sample.pb.go license header year, which the
Format Check's licenseupdater rewrites (the previous push's only CI
failure).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The conformance suite's protovalidate dependency was added at @latest
(v1.2.0, requiring cel-go 0.28). Consumers that pin this module as a Go
tool (cloudv2) then get MVS-bumped to protovalidate v1.2.0, whose
violation MESSAGE WORDING changed (e.g. "value must contain at least 1
item(s)" became "must contain at least 1 item(s)"), breaking every
downstream test that asserts exact violation strings — ~15 failures
across unrelated cloudv2 services.

Pin to v1.0.0 / cel-go 0.26.1, matching cloudv2's resolution. The
suite's own assertions use structured rule ids, not message text, and
pass unchanged on both versions; v1.0.0 just stops this module from
forcing a protovalidate behavior change on consumers. Revisit when the
ecosystem moves to v1.2+ deliberately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same consumer-compatibility rationale as the protovalidate-go pin: the
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go snapshot
carries the rule MESSAGE TEXT protovalidate renders, and the 2026-04
snapshot rewrote it (e.g. "value must be a valid HTTP header value"
became "must be a valid HTTP header value"). Any consumer pinning this
module gets MVS-bumped and every downstream test asserting exact
violation strings breaks.

Pin to the 2025-09 snapshot cloudv2 main resolves. Our own assertions
use structured rule ids and field paths, not upstream message text;
the full suite passes identically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sago2k8 sago2k8 merged commit 4ebcf3a into main Jul 7, 2026
31 checks passed
@sago2k8 sago2k8 deleted the sj/ocsf-protogen branch July 7, 2026 08:04
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.

4 participants