Conformance test suite for Policy spec implementations. Verifies that policy evaluation engines in Go, Rust, and Zig produce identical results when given the same policies and OpenTelemetry input data.
.
├── Taskfile.yml # Test harness (build, test, bench, clean)
├── runners/
│ ├── go/ # Go conformance runner (policy-go)
│ ├── rs/ # Rust conformance runner (policy-rs)
│ └── zig/ # Zig conformance runner (policy-zig)
├── server/ # HTTP/gRPC conformance server (Go)
├── testcases/ # 184 test case directories
│ ├── logs_*/ # Log signal tests
│ ├── metrics_*/ # Metric signal tests
│ ├── traces_*/ # Trace signal tests
│ └── compound_*/ # Multi-signal / multi-batch tests
└── bin/ # Hermit-managed toolchain
Each test case is a directory under testcases/ containing:
Simple test (single input/output):
testcases/logs_severity_drop/
├── policies.json # Policy definitions
├── input.json # OTLP JSON input
├── expected.json # Expected OTLP JSON output
├── expected_stats.json # Expected match statistics
└── expected_volume.json # Optional; expected SyncRequest.volume (HTTP/gRPC only)
The harness runs a simple test as follows:
- Detect signal type from the directory name prefix (
logs_*→ log,metrics_*→ metric,traces_*→ trace) - Invoke the runner:
./runner-go --policies policies.json --input input.json \ --output output_go.json --stats stats_go.json --signal log - Normalize both
expected.jsonandoutput_go.jsonwith jq (strip null fields, coerce numeric strings to numbers, sort keys) - Diff the normalized output against
expected.json— any difference is a failure - Diff
stats_go.jsonagainstexpected_stats.json— any difference is a failure - Report PASS or FAIL (on failure, print the diff)
Compound test (multiple batches, stats checked once after all batches):
testcases/compound_mixed_signals/
├── policies.json # Policy definitions (may span signals)
├── input_1.json # Batch 1 (e.g., logs)
├── expected_1.json # Expected output for batch 1
├── input_2.json # Batch 2 (e.g., metrics)
├── expected_2.json # Expected output for batch 2
├── input_3.json # Batch 3 (e.g., traces)
├── expected_3.json # Expected output for batch 3
└── expected_stats.json # Merged stats across all batches
The harness runs a compound test as follows:
- Iterate over
input_N.jsonfiles sorted numerically - For each batch N:
- Detect signal type from the JSON content (
resourceLogs→ log,resourceMetrics→ metric,resourceSpans→ trace) - Invoke the runner with
input_N.json, writingoutput_N_go.jsonandstats_N_go.json - Normalize and diff
output_N_go.jsonagainstexpected_N.json
- Detect signal type from the JSON content (
- After all batches, merge per-batch stats files with jq (sum
hitsandmissesperpolicy_id) - Diff the merged stats against
expected_stats.json - Report PASS or FAIL (on failure, print diffs for each failing batch and/or stats)
All three runners implement the same CLI interface:
runner-{go,rs,zig} \
--policies policies.json \
--input input.json \
--output output.json \
--stats stats.json \
--signal {log,metric,trace}
For HTTP/gRPC mode, --policies is replaced with --server URL or
--grpc ADDR.
| Runner | Language | Policy engine | Protobuf codec |
|---|---|---|---|
runner-go |
Go | policy-go |
protojson |
runner-rs |
Rust | policy-rs |
serde + custom OTel types |
runner-zig |
Zig | policy-zig |
Native proto JSON codec |
- Task (provided via
bin/) - Go 1.26+
- Rust (stable)
- Zig 0.15+
- Hyperscan/Vectorscan (
task ci:setupinstalls it) jq(provided viabin/)
task build # Build all runners + conformance serverRuns each runner as a CLI process that reads policies from a local JSON file:
task test # Run all 3 runners
task test:go # Go only
task test:rs # Rust only
task test:zig # Zig onlyRuns the conformance server per test case, then uses the runner's --server
flag to fetch policies over HTTP:
task test:http # All 3 runners via HTTP
task test:http:go # Go only
task test:http:rs # Rust only
task test:http:zig # Zig onlyWhat happens for each http test case:
- Starts the conformance server on a random port:
./conformance-server --policies testcases/X/policies.json --http-port 0 --grpc-port 0 - Reads
HTTP_PORT=NandGRPC_PORT=Nfrom stdout (via FIFO, no sleeps) - Invokes the runner:
./runner-go --server http://localhost:PORT/v1/policy/sync \ --input testcases/X/input.json \ --output testcases/X/output_go_http.json \ --signal log - Fetches accumulated stats from
GET /stats - For test cases with an
expected_volume.json, fetches the accumulatedSyncRequest.volumefromGET /volume - Shuts down the server via
GET /shutdown - Diffs output and stats (stats normalization strips
missesand zero-hit policies since the server only reports{policy_id, hits}for matched policies), plus volume when expected
Volume only reaches a provider (SyncRequest.volume), so it is checked in
HTTP/gRPC mode only — file mode ignores expected_volume.json. The server sums
the volume of every sync it receives; clients drain their counters on read, so
the sum is the total the client observed:
{ "log_records": 3, "metric_data_points": 3, "spans": 3 }Only record counts are compared, and omitted fields read as 0. Byte counts are
optional per spec and explicitly an estimate, so they are not comparable across
implementations — the runners leave them at 0.
Same as HTTP but uses --grpc flag:
task test:grpc # Go + Rust via gRPC
task test:grpc:go
task test:grpc:rsNote: The Zig runner does not support gRPC.
Runs whole binaries (not just the runners) end to end: the harness starts the implementation with a rendered config, POSTs each testcase over OTLP, and diffs what reaches a local sink.
task test:impl # collector + edge + vector
task test:impl:collector # tero-collector-distro only
task test:impl:edge # edge only
task test:impl:vector # Vector onlyedge and tero-collector-distro are provisioned from their upstream releases,
pinned by EDGE_VERSION / COLLECTOR_VERSION in Taskfile.yml; the
test:impl:* tasks fetch them on demand and skip the fetch when the pinned
version is already in place. To try a different release without editing the
Taskfile:
task fetch # provision both at their pinned versions
task fetch:edge EDGE_VERSION=latest
task fetch:collector COLLECTOR_VERSION=v0.13.0tero-collector-distro publishes only a container image, so fetch:collector
builds the tag from source with OCB (a few minutes, and it needs Go). Vector is
still a hand-placed binary — see implementations/vector/README.md.
task test:repeat TC=traces_sampling_50pct N=100 R=go # Repeat one test N times
task bench # Benchmark all runners with hyperfine
task clean # Remove build artifacts and outputstraces_event_attributeandtraces_link_trace_idare unimplemented across all runnerstask test:http:rserrors on every test case with policy-rs v1.8.0: its vendoredPolicymessage has noextensionsfield (spec v1.6.0), and its JSON decoder rejects unknown fields, so it rejects the server's sync response. gRPC mode is unaffected (protobuf ignores unknown fields).
All 184 test cases listed below pass for all three runners (Go, Rust, Zig) in both file-based and HTTP modes unless noted otherwise.
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
logs_all_dropped |
All log records match a drop policy; output is empty | ✅ | ✅ | ✅ |
logs_attribute_match |
Match log records by log attribute exact value | ✅ | ✅ | ✅ |
logs_case_insensitive_ends_with |
Case-insensitive ends_with matcher on severity |
✅ | ✅ | ✅ |
logs_case_insensitive_exact |
Case-insensitive exact matcher on body |
✅ | ✅ | ✅ |
logs_case_insensitive_regex |
Case-insensitive regex matcher on body | ✅ | ✅ | ✅ |
logs_case_insensitive_starts_with |
Case-insensitive starts_with matcher on severity |
✅ | ✅ | ✅ |
logs_contains_ci |
Case-insensitive contains matcher on body |
✅ | ✅ | ✅ |
logs_contains_cs |
Case-sensitive contains matcher on body |
✅ | ✅ | ✅ |
logs_empty_input |
Empty input (no log records) produces empty output | ✅ | ✅ | ✅ |
logs_empty_vs_missing_field |
Distinguish between empty string and missing/null field | ✅ | ✅ | ✅ |
logs_enabled_false |
Disabled policy (enabled: false) is skipped entirely |
✅ | ✅ | ✅ |
logs_enabled_false_with_transforms |
Disabled policy with transforms; transforms must not fire | ✅ | ✅ | ✅ |
logs_ends_with |
ends_with matcher on severity text |
✅ | ✅ | ✅ |
logs_event_name_field |
Match on the event_name log field |
✅ | ✅ | ✅ |
logs_exact_drop |
Exact match on severity drops matching records | ✅ | ✅ | ✅ |
logs_exists |
exists: true matcher on log attribute |
✅ | ✅ | ✅ |
logs_exists_false |
exists: false matcher — match when attribute is absent |
✅ | ✅ | ✅ |
logs_keep_all_default |
Policy with keep: "all" passes all matched records through |
✅ | ✅ | ✅ |
logs_multiple_matchers |
Policy with multiple matchers (AND logic) | ✅ | ✅ | ✅ |
logs_multiple_policies_most_restrictive |
Most-restrictive keep wins when multiple policies match | ✅ | ✅ | ✅ |
logs_multiple_resources |
Multiple resources in input processed independently | ✅ | ✅ | ✅ |
logs_negated_match |
negate: true inverts a matcher |
✅ | ✅ | ✅ |
logs_nested_attribute |
Match on nested attribute path (e.g., ["http", "method"]) |
✅ | ✅ | ✅ |
logs_nested_attribute_deep |
Match on deeply nested attribute path (3+ levels) | ✅ | ✅ | ✅ |
logs_no_match |
No policy matches; all records pass through unmodified | ✅ | ✅ | ✅ |
logs_overlapping_policies |
Multiple policies match the same record | ✅ | ✅ | ✅ |
logs_policy_ordering_determinism |
Policies evaluated in deterministic (alphanumeric by ID) order | ✅ | ✅ | ✅ |
logs_regex_drop |
Regex matcher drops matching records | ✅ | ✅ | ✅ |
logs_resource_attr |
Match on resource attribute | ✅ | ✅ | ✅ |
logs_resource_schema_url |
Match on resource schema URL | ✅ | ✅ | ✅ |
logs_scope_attr |
Match on scope attribute | ✅ | ✅ | ✅ |
logs_scope_schema_url |
Match on scope schema URL | ✅ | ✅ | ✅ |
logs_severity_drop |
Drop by severity text exact match | ✅ | ✅ | ✅ |
logs_span_id_field |
Match on the span_id log field |
✅ | ✅ | ✅ |
logs_starts_with |
starts_with matcher on severity text |
✅ | ✅ | ✅ |
logs_three_matchers |
Three matchers combined in a single policy (AND) | ✅ | ✅ | ✅ |
logs_trace_id_field |
Match on the trace_id log field |
✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
logs_rate_limit |
Rate limit to N records per second | ✅ | ✅ | ✅ |
logs_rate_limit_drop_overlap |
keep: "none" overrides rate limit on the same record |
✅ | ✅ | ✅ |
logs_rate_limit_per_minute |
Rate limit specified as N per minute | ✅ | ✅ | ✅ |
logs_sample_key_attribute |
Sampling keyed by log attribute value | ✅ | ✅ | ✅ |
logs_sample_key_resource_attr |
Sampling keyed by resource attribute value | ✅ | ✅ | ✅ |
logs_sample_key_scope_attr |
Sampling keyed by scope attribute value | ✅ | ✅ | ✅ |
logs_sampling_10pct |
10% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_25pct |
25% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_50pct |
50% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_75pct |
75% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_drop_overlap |
keep: "none" overrides sampling on the same record |
✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
logs_transform_add_attr_upsert_absent |
Add attribute with upsert: true when field absent (insert) |
✅ | ✅ | ✅ |
logs_transform_add_attribute |
Add a new log attribute | ✅ | ✅ | ✅ |
logs_transform_add_body |
Add body to log record when body is null | ✅ | ✅ | ✅ |
logs_transform_add_body_no_upsert_exists |
Add body without upsert when body exists (no-op) | ✅ | ✅ | ✅ |
logs_transform_add_body_upsert_exists |
Add body with upsert: true when body exists (overwrite) |
✅ | ✅ | ✅ |
logs_transform_add_no_upsert |
Add attribute with upsert: false when field exists (no-op) |
✅ | ✅ | ✅ |
logs_transform_add_no_upsert_new_field |
Add attribute with upsert: false when field absent (insert) |
✅ | ✅ | ✅ |
logs_transform_add_resource_attr |
Add a new resource attribute | ✅ | ✅ | ✅ |
logs_transform_add_scope_attr |
Add a new scope attribute | ✅ | ✅ | ✅ |
logs_transform_add_upsert |
Add attribute with upsert: true overwrites existing value |
✅ | ✅ | ✅ |
logs_transform_drop_skips_transform |
Transforms are not applied to records dropped by keep: "none" |
✅ | ✅ | ✅ |
logs_transform_execution_order |
Transforms execute in spec order: remove → redact → rename → add | ✅ | ✅ | ✅ |
logs_transform_multiple_policies |
Multiple policies each apply their own transforms | ✅ | ✅ | ✅ |
logs_transform_multiple_same_field |
Multiple transforms targeting the same field in one policy | ✅ | ✅ | ✅ |
logs_transform_redact_attribute |
Redact a log attribute value with [REDACTED] |
✅ | ✅ | ✅ |
logs_transform_redact_body |
Redact the log body field | ✅ | ✅ | ✅ |
logs_transform_redact_nonexistent |
Redact a non-existent field (no-op) | ✅ | ✅ | ✅ |
logs_transform_redact_resource_attr |
Redact a resource attribute | ✅ | ✅ | ✅ |
logs_transform_redact_scope_attr |
Redact a scope attribute | ✅ | ✅ | ✅ |
logs_transform_remove_attribute |
Remove a log attribute | ✅ | ✅ | ✅ |
logs_transform_remove_body |
Remove the log body field | ✅ | ✅ | ✅ |
logs_transform_remove_nonexistent |
Remove a non-existent field (no-op) | ✅ | ✅ | ✅ |
logs_transform_remove_resource_attr |
Remove a resource attribute | ✅ | ✅ | ✅ |
logs_transform_remove_scope_attr |
Remove a scope attribute | ✅ | ✅ | ✅ |
logs_transform_rename_attribute |
Rename a log attribute | ✅ | ✅ | ✅ |
logs_transform_rename_no_upsert |
Rename with upsert: false when target exists (no-op) |
✅ | ✅ | ✅ |
logs_transform_rename_nonexistent |
Rename a non-existent source attribute (no-op) | ✅ | ✅ | ✅ |
logs_transform_rename_resource_attr |
Rename a resource attribute | ✅ | ✅ | ✅ |
logs_transform_rename_scope_attr |
Rename a scope attribute | ✅ | ✅ | ✅ |
logs_transform_rename_source_absent |
Rename when source attribute absent, upsert: false (no-op) |
✅ | ✅ | ✅ |
logs_transform_rename_target_absent |
Rename when target absent, upsert: false (normal rename) |
✅ | ✅ | ✅ |
logs_transform_rename_upsert |
Rename with upsert: true overwrites existing target |
✅ | ✅ | ✅ |
logs_transform_rename_upsert_source_absent |
Rename with upsert: true when source absent (no-op) |
✅ | ✅ | ✅ |
logs_transform_rename_upsert_target_absent |
Rename with upsert: true when target absent (rename) |
✅ | ✅ | ✅ |
logs_transform_with_rate_limit |
Transforms applied to records that survive rate limiting | ✅ | ✅ | ✅ |
logs_transform_with_sampling |
Transforms applied to records that survive sampling | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
metrics_aggregation_temporality |
Match by aggregation temporality (delta/cumulative) | ✅ | ✅ | ✅ |
metrics_case_insensitive |
Case-insensitive metric name matching | ✅ | ✅ | ✅ |
metrics_cumulative_temporality |
Match cumulative temporality specifically | ✅ | ✅ | ✅ |
metrics_description |
Match by metric description field | ✅ | ✅ | ✅ |
metrics_drop_by_attr |
Drop metrics by datapoint attribute | ✅ | ✅ | ✅ |
metrics_drop_by_name |
Drop metrics by name exact match | ✅ | ✅ | ✅ |
metrics_empty_input |
Empty input (no metrics) produces empty output | ✅ | ✅ | ✅ |
metrics_ends_with |
ends_with matcher on metric name |
✅ | ✅ | ✅ |
metrics_exists |
exists: true matcher on datapoint attribute |
✅ | ✅ | ✅ |
metrics_exists_false |
exists: false matcher on datapoint attribute |
✅ | ✅ | ✅ |
metrics_exponential_histogram_type |
Match exponential histogram metric type | ✅ | ✅ | ✅ |
metrics_histogram_type |
Match histogram metric type | ✅ | ✅ | ✅ |
metrics_keep |
Basic keep policy for metrics | ✅ | ✅ | ✅ |
metrics_multiple_matchers |
Multiple matchers combined (AND logic) for metrics | ✅ | ✅ | ✅ |
metrics_multiple_policies |
Multiple metric policies evaluated together | ✅ | ✅ | ✅ |
metrics_multiple_resources |
Multiple resources in metric input | ✅ | ✅ | ✅ |
metrics_negate |
Negated matcher for metrics | ✅ | ✅ | ✅ |
metrics_negate_temporality |
Negated temporality matcher | ✅ | ✅ | ✅ |
metrics_negate_type |
Negated metric type matcher | ✅ | ✅ | ✅ |
metrics_overlapping_miss |
Overlapping policies where one misses | ✅ | ✅ | ✅ |
metrics_resource_attr |
Match on resource attribute for metrics | ✅ | ✅ | ✅ |
metrics_resource_schema_url |
Match on resource schema URL for metrics | ✅ | ✅ | ✅ |
metrics_scope_attr |
Match on scope attribute for metrics | ✅ | ✅ | ✅ |
metrics_scope_name |
Match on scope name for metrics | ✅ | ✅ | ✅ |
metrics_scope_schema_url |
Match on scope schema URL for metrics | ✅ | ✅ | ✅ |
metrics_scope_version |
Match on scope version for metrics | ✅ | ✅ | ✅ |
metrics_starts_with |
starts_with matcher on metric name |
✅ | ✅ | ✅ |
metrics_sum_type |
Match sum metric type | ✅ | ✅ | ✅ |
metrics_summary_type |
Match summary metric type | ✅ | ✅ | ✅ |
metrics_three_policies |
Three metric policies evaluated together | ✅ | ✅ | ✅ |
metrics_type_filter |
Filter metrics by type (gauge/sum/histogram) | ✅ | ✅ | ✅ |
metrics_unit |
Match by metric unit field | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
traces_case_insensitive |
Case-insensitive span name matching | ✅ | ✅ | ✅ |
traces_drop_0pct |
Drop at 0% (all sampled out) | ✅ | ✅ | ✅ |
traces_empty_input |
Empty input (no spans) produces empty output | ✅ | ✅ | ✅ |
traces_error_vs_health |
Distinguish error spans from health check spans | ✅ | ✅ | ✅ |
traces_event_name |
Match on span event name | ✅ | ✅ | ✅ |
traces_exists |
exists: true matcher on span attribute |
✅ | ✅ | ✅ |
traces_exists_false |
exists: false matcher on span attribute |
✅ | ✅ | ✅ |
traces_multiple_matchers |
Multiple matchers combined (AND) for traces | ✅ | ✅ | ✅ |
traces_multiple_resources |
Multiple resources in trace input | ✅ | ✅ | ✅ |
traces_name_contains |
contains matcher on span name |
✅ | ✅ | ✅ |
traces_name_ends_with |
ends_with matcher on span name |
✅ | ✅ | ✅ |
traces_name_regex |
Regex matcher on span name | ✅ | ✅ | ✅ |
traces_name_starts_with |
starts_with matcher on span name |
✅ | ✅ | ✅ |
traces_negate |
Negated matcher for traces | ✅ | ✅ | ✅ |
traces_negate_span_kind |
Negated span kind matcher | ✅ | ✅ | ✅ |
traces_negate_span_status |
Negated span status matcher | ✅ | ✅ | ✅ |
traces_overlapping |
Overlapping trace policies on the same spans | ✅ | ✅ | ✅ |
traces_parent_span_id |
Match on parent span ID field | ✅ | ✅ | ✅ |
traces_resource_attr |
Match on resource attribute for traces | ✅ | ✅ | ✅ |
traces_scope_attr |
Match on scope attribute for traces | ✅ | ✅ | ✅ |
traces_scope_name |
Match on scope name for traces | ✅ | ✅ | ✅ |
traces_scope_schema_url |
Match on scope schema URL for traces | ✅ | ✅ | ✅ |
traces_scope_version |
Match on scope version for traces | ✅ | ✅ | ✅ |
traces_span_attribute |
Match on span attribute exact value | ✅ | ✅ | ✅ |
traces_span_attribute_contains |
contains matcher on span attribute |
✅ | ✅ | ✅ |
traces_span_kind |
Match on span kind (server) | ✅ | ✅ | ✅ |
traces_span_kind_client |
Match on span kind (client) | ✅ | ✅ | ✅ |
traces_span_kind_consumer |
Match on span kind (consumer) | ✅ | ✅ | ✅ |
traces_span_kind_producer |
Match on span kind (producer) | ✅ | ✅ | ✅ |
traces_span_status_error |
Match on span status = error | ✅ | ✅ | ✅ |
traces_span_status_ok |
Match on span status = ok | ✅ | ✅ | ✅ |
traces_span_status_unset |
Match on span status = unset | ✅ | ✅ | ✅ |
traces_trace_state |
Match on trace state field | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
traces_keep_100pct |
Keep at 100% (all sampled in, writes th:0 tracestate) |
✅ | ✅ | ✅ |
traces_sampling_10pct |
10% trace sampling with deterministic hash | ✅ | ✅ | ✅ |
traces_sampling_25pct |
25% trace sampling | ✅ | ✅ | ✅ |
traces_sampling_50pct |
50% trace sampling | ✅ | ✅ | ✅ |
traces_sampling_75pct |
75% trace sampling | ✅ | ✅ | ✅ |
traces_sampling_equalizing |
Equalizing sampling algorithm | ✅ | ✅ | ✅ |
traces_sampling_fail_closed |
fail_closed: true drops spans without valid trace ID |
✅ | ✅ | ✅ |
traces_sampling_precision |
High-precision sampling threshold encoding | ✅ | ✅ | ✅ |
traces_sampling_proportional |
Proportional sampling algorithm | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
traces_tracestate_equalizing_incoming_th |
Equalizing sampler with pre-existing th in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_fail_closed_true |
Fail-closed behavior with tracestate present | ✅ | ✅ | ✅ |
traces_tracestate_mixed |
Mixed tracestate scenarios (some with, some without) | ✅ | ✅ | ✅ |
traces_tracestate_overwrite_ot |
Overwrite existing ot vendor key in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_preserve_vendors |
Preserve non-ot vendor keys in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_proportional_incoming_th |
Proportional sampler with pre-existing th in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_rv_consistency_check |
Randomness value consistency check in tracestate | ✅ | ✅ | ✅ |
traces_tracestate_rv_randomness |
Random value (rv) written to tracestate |
✅ | ✅ | ✅ |
traces_tracestate_write_basic |
Basic tracestate write with sampling threshold | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
compound_all_keep_types |
All keep types (all/none/sample/rate_limit) in one policy set | ✅ | ✅ | ✅ |
compound_conflicting_keeps |
Conflicting keep decisions across policies; most restrictive wins | ✅ | ✅ | ✅ |
compound_datapoint_attr_types |
Datapoint attribute matching across histogram, summary, gauge | ✅ | ✅ | ✅ |
compound_disabled_mixed |
Mix of enabled and disabled policies; disabled transforms must not fire | ✅ | ✅ | ✅ |
compound_double_negation |
exists: false + negate: true semantics |
✅ | ✅ | ✅ |
compound_empty_vs_missing |
Empty string vs null/absent field behavior across signals | ✅ | ✅ | ✅ |
compound_many_policies_fanout |
50+ policies each targeting a different service; verifies no cross-contamination | ✅ | ✅ | ✅ |
compound_mixed_signals |
Policies spanning logs, metrics, and traces in one set | ✅ | ✅ | ✅ |
compound_negation_overlap |
Policies with negated matchers creating complex intersections | ✅ | ✅ | ✅ |
compound_nested_attributes |
Nested attribute paths across all signal types | ✅ | ✅ | ✅ |
compound_rate_limit_most_restrictive |
keep: "none" overrides rate limit; confirms none > rate_limit |
✅ | ✅ | ✅ |
compound_regex_edge_cases |
Character classes, anchoring, alternation, UUID patterns, escaped brackets | ✅ | ✅ | ✅ |
compound_sampling_interactions |
Trace sampling with fail_closed, 0% drop overriding sampling | ✅ | ✅ | ✅ |
compound_scope_isolation |
Resource/scope attribute transforms are isolated per scope | ✅ | ✅ | ✅ |
compound_transform_chain |
Cross-policy transform visibility (transforms applied after all matching) | ✅ | ✅ | ✅ |
compound_transform_ordering_alphanumeric |
Policies evaluated in alphanumeric ID order, not array order | ✅ | ✅ | ✅ |
compound_transforms_across_policies |
Multiple policies with add/redact/rename transforms on same records | ✅ | ✅ | ✅ |
| Signal | Tests | Go | Zig | Rust |
|---|---|---|---|---|
| Logs | 84 | 84 | 84 | 84 |
| Metrics | 32 | 32 | 32 | 32 |
| Traces | 51 | 51 | 51 | 51 |
| Compound | 17 | 16 | 17 | 16 |
| Total | 184 | 183 | 184 | 183 |