Skip to content

feat: field-level envelope encryption for step.event_publish + step.event_decrypt#313

Merged
intel352 merged 7 commits intomainfrom
copilot/feature-field-level-encryption
Mar 12, 2026
Merged

feat: field-level envelope encryption for step.event_publish + step.event_decrypt#313
intel352 merged 7 commits intomainfrom
copilot/feature-field-level-encryption

Conversation

Copy link
Contributor

Copilot AI commented Mar 12, 2026

step.event_publish had no encryption support, making it unsuitable for compliance-sensitive workloads (PII/PHI) where specific fields must be encrypted before hitting the transport layer.

Changes

step.event_publish — optional encryption config

  • Accepts an encryption block; silently ignored when key_id or fields are absent (fully backward-compatible)
  • Generates a random 32-byte DEK per event, encrypts specified fields with AES-256-GCM
  • Wraps the DEK with the resolved master key (AES-256-GCM)
  • Stores metadata as CloudEvents extension attributes: encryption, keyid, encrypteddek, encryptedfields
  • key_id must be an environment variable reference ($VAR or ${VAR}) — literal strings are rejected to prevent operators from accidentally using non-secret identifiers (e.g. KMS ARNs) as key material
  • provider must be aes or envelope; algorithm must be AES-256-GCM — unsupported values return a config error at step creation time
  • broker or provider is required when encryption is configured — prevents publishing ciphertext via the EventBus path without the decryption metadata extensions
- name: publish
  type: step.event_publish
  config:
    topic: follow-up.created
    source: chimera-api
    event_type: patient.contact
    broker: my-broker          # required when encryption is enabled
    encryption:
      provider: aes            # "aes" or "envelope"
      key_id: "${KMS_KEY_ARN}" # must be an env-var reference: $VAR or ${VAR}
      fields: [phone, message_body, responder_name]
      algorithm: AES-256-GCM

step.event_decrypt — new consumer-side step

  • Reads encrypteddek, encryptedfields, keyid extensions from the incoming CloudEvent in pc.Current
  • Validates the encryption extension (algorithm) before decryption — unsupported algorithms return a clear error rather than producing garbage output
  • Unwraps the DEK, decrypts only the named fields; non-encrypted events (missing metadata) pass through unchanged
  • key_id config overrides the keyid extension (useful when the master key lives in a different env on the consumer side)
- name: decrypt
  type: step.event_decrypt
  config:
    key_id: "${CONSUMER_KEY}"  # optional override; falls back to keyid extension

Supporting internals (pipeline_step_event_encryption.go)

  • resolveEncryptionKey — resolves master key exclusively from $VAR or ${VAR} env references; literal key strings are rejected
  • applyEventFieldEncryption / decryptEventFields — stateless helpers shared by both steps
  • Per-event DEK isolation: compromise of one event's ciphertext does not expose others
Original prompt

This section details on the original issue you should resolve

<issue_title>Feature: field-level encryption support for step.event_publish</issue_title>
<issue_description>## Summary

step.event_publish currently publishes CloudEvents without any encryption support. For compliance-sensitive workloads (PII, PHI), field-level encryption is needed before events hit the transport layer.

Use Case

Healthcare/crisis applications need to encrypt specific fields (e.g., phone numbers, message content) within CloudEvent payloads using envelope encryption (KMS + DEK pattern) before publishing to Kinesis/SQS/Kafka. The encrypted event should conform to CloudEvents v1.0 with encryption metadata stored as extensions.

Proposed Approach

Add optional encryption config to step.event_publish:

- name: publish
  type: step.event_publish
  config:
    topic: follow-up.created
    source: chimera-api
    encryption:
      provider: kms          # or "aes", "envelope"
      key_id: "${KMS_KEY_ARN}"
      fields: [phone, message_body, responder_name]
      algorithm: AES-256-GCM

The step would:

  1. Generate a per-event DEK
  2. Encrypt specified fields individually (preserving event structure)
  3. Store encrypted DEK + field metadata as CloudEvents extensions (encryption, keyid, encrypteddek, encryptedfields)
  4. Publish the modified event

A corresponding decryption utility or step (step.event_decrypt?) would reverse the process on the consumer side.

Alternatives Considered

  • Application-level encryption before step: Works but duplicates logic across every pipeline that publishes sensitive events
  • Transport-level encryption (TLS/KMS on Kinesis): Encrypts in transit/at rest but not at the field level — all consumers see all fields
  • Bento processor: Could use a custom Bloblang mapping, but encryption logic doesn't belong in stream transforms

Context

The modular EventBus interface and KinesisEventBus handle transport. This feature is about the pipeline step adding encryption before handing off to the EventBus.</issue_description>

<agent_instructions>Also ensure that we have the ability to consume an encrypted event, and apply decryption.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

…_decrypt

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copilot AI changed the title [WIP] Add field-level encryption support for step.event_publish feat: field-level envelope encryption for step.event_publish + step.event_decrypt Mar 12, 2026
@intel352 intel352 marked this pull request as ready for review March 12, 2026 15:59
Copilot AI review requested due to automatic review settings March 12, 2026 15:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds optional field-level (envelope-style) encryption to step.event_publish and introduces a new step.event_decrypt pipeline step to decrypt encrypted CloudEvent payload fields on the consumer side.

Changes:

  • Extend step.event_publish with an optional encryption config block and emit encryption metadata via CloudEvents-style extension attributes.
  • Add shared AES-GCM encryption/decryption helpers (DEK generation + wrapping).
  • Add step.event_decrypt and corresponding unit tests; register the new step in the pipelinesteps plugin.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plugins/pipelinesteps/plugin.go Registers step.event_decrypt in plugin step lists and step factory map.
module/pipeline_step_event_publish.go Parses encryption config, encrypts configured payload fields, and attaches encryption metadata to the published envelope.
module/pipeline_step_event_encryption.go Implements key resolution, DEK generation, AES-GCM encrypt/decrypt helpers, and payload field encryption/decryption utilities.
module/pipeline_step_event_decrypt.go Implements consumer-side decryption step that reads encryption metadata from the current event and decrypts specified fields.
module/pipeline_step_event_decrypt_test.go Adds unit/integration tests for helpers plus publish→decrypt round-trips.

Comment on lines +72 to +85
// Read encryption extension attributes.
encryptedDEKB64, _ := event["encrypteddek"].(string)
encryptedFields, _ := event["encryptedfields"].(string)
keyID, _ := event["keyid"].(string)

// Override keyID from step configuration if provided.
if s.keyID != "" {
keyID = s.keyID
}

// If the event has no encryption metadata, pass through unchanged.
if encryptedDEKB64 == "" || encryptedFields == "" || keyID == "" {
return &StepResult{Output: event}, nil
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

step.event_decrypt decrypts whenever encrypteddek/encryptedfields/keyid are present, but it does not check the encryption extension (algorithm/provider). That makes it easy to accidentally attempt decryption on events produced by a different scheme, yielding confusing failures. Consider validating event["encryption"] (e.g., require "AES-256-GCM") and either return a clear error or passthrough when it’s missing/unsupported.

Copilot uses AI. Check for mistakes.
Comment on lines 168 to 172
// Build event envelope for broker/EventPublisher paths
event := s.buildEventEnvelope(resolvedPayload, resolvedHeaders, resolvedSource)
event := s.buildEventEnvelope(resolvedPayload, resolvedHeaders, resolvedSource, encMeta)

if s.broker != "" {
// Try EventPublisher interface first (supports external plugins like Bento)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

With encryption configured, this code encrypts resolvedPayload and computes encMeta, but the EventBus publish path later sends only resolvedPayload (not the envelope/extensions). That would publish ciphertext without encrypteddek/encryptedfields/keyid, so consumers cannot decrypt. Consider either requiring a broker/provider when encryption is enabled, or extending the EventBus path to carry the same encryption metadata.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +101
enc := &EventEncryptionConfig{
Provider: "aes",
Algorithm: "AES-256-GCM",
}
if p, ok := encCfg["provider"].(string); ok && p != "" {
enc.Provider = p
}
if k, ok := encCfg["key_id"].(string); ok {
enc.KeyID = k
}
if a, ok := encCfg["algorithm"].(string); ok && a != "" {
enc.Algorithm = a
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The encryption.provider and encryption.algorithm config values are parsed and then effectively ignored by the encryption helpers (encryption is always AES-GCM and the DEK is always 32 bytes). This can mislead operators (e.g., algorithm set to something else will be reported in the event metadata but not actually used). Consider validating provider/algorithm against the supported set and returning a config error (or overriding them to the actual values used).

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Mar 12, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:244: parsing iteration count: invalid syntax
baseline-bench.txt:322724: parsing iteration count: invalid syntax
baseline-bench.txt:617814: parsing iteration count: invalid syntax
baseline-bench.txt:923491: parsing iteration count: invalid syntax
baseline-bench.txt:1237034: parsing iteration count: invalid syntax
baseline-bench.txt:1546458: parsing iteration count: invalid syntax
benchmark-results.txt:244: parsing iteration count: invalid syntax
benchmark-results.txt:365747: parsing iteration count: invalid syntax
benchmark-results.txt:709794: parsing iteration count: invalid syntax
benchmark-results.txt:1069174: parsing iteration count: invalid syntax
benchmark-results.txt:1466894: parsing iteration count: invalid syntax
benchmark-results.txt:1838841: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │
                            │       sec/op       │
InterpreterCreation-4               6.105m ± 60%
ComponentLoad-4                     3.483m ±  3%
ComponentExecute-4                  2.008µ ±  2%
PoolContention/workers-1-4          1.093µ ±  3%
PoolContention/workers-2-4          1.081µ ±  1%
PoolContention/workers-4-4          1.083µ ±  3%
PoolContention/workers-8-4          1.088µ ±  1%
PoolContention/workers-16-4         1.090µ ±  3%
ComponentLifecycle-4                3.481m ±  0%
SourceValidation-4                  2.308µ ±  0%
RegistryConcurrent-4                786.9n ±  4%
LoaderLoadFromString-4              3.507m ±  0%
geomean                             18.33µ

                            │ baseline-bench.txt │
                            │        B/op        │
InterpreterCreation-4               1.944Mi ± 0%
ComponentLoad-4                     2.097Mi ± 0%
ComponentExecute-4                  1.203Ki ± 0%
PoolContention/workers-1-4          1.203Ki ± 0%
PoolContention/workers-2-4          1.203Ki ± 0%
PoolContention/workers-4-4          1.203Ki ± 0%
PoolContention/workers-8-4          1.203Ki ± 0%
PoolContention/workers-16-4         1.203Ki ± 0%
ComponentLifecycle-4                2.099Mi ± 0%
SourceValidation-4                  1.984Ki ± 0%
RegistryConcurrent-4                1.133Ki ± 0%
LoaderLoadFromString-4              2.099Mi ± 0%
geomean                             15.05Ki

                            │ baseline-bench.txt │
                            │     allocs/op      │
InterpreterCreation-4                15.09k ± 0%
ComponentLoad-4                      17.43k ± 0%
ComponentExecute-4                    25.00 ± 0%
PoolContention/workers-1-4            25.00 ± 0%
PoolContention/workers-2-4            25.00 ± 0%
PoolContention/workers-4-4            25.00 ± 0%
PoolContention/workers-8-4            25.00 ± 0%
PoolContention/workers-16-4           25.00 ± 0%
ComponentLifecycle-4                 17.48k ± 0%
SourceValidation-4                    32.00 ± 0%
RegistryConcurrent-4                  2.000 ± 0%
LoaderLoadFromString-4               17.47k ± 0%
geomean                               181.2

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                            │ benchmark-results.txt │
                            │        sec/op         │
InterpreterCreation-4                  6.543m ± 56%
ComponentLoad-4                        3.308m ±  1%
ComponentExecute-4                     1.887µ ±  3%
PoolContention/workers-1-4             1.193µ ±  4%
PoolContention/workers-2-4             1.196µ ±  1%
PoolContention/workers-4-4             1.200µ ±  1%
PoolContention/workers-8-4             1.197µ ±  1%
PoolContention/workers-16-4            1.216µ ±  2%
ComponentLifecycle-4                   3.552m ±  3%
SourceValidation-4                     2.293µ ±  1%
RegistryConcurrent-4                   988.4n ±  5%
LoaderLoadFromString-4                 3.455m ±  2%
geomean                                19.39µ

                            │ benchmark-results.txt │
                            │         B/op          │
InterpreterCreation-4                  1.944Mi ± 0%
ComponentLoad-4                        2.097Mi ± 0%
ComponentExecute-4                     1.203Ki ± 0%
PoolContention/workers-1-4             1.203Ki ± 0%
PoolContention/workers-2-4             1.203Ki ± 0%
PoolContention/workers-4-4             1.203Ki ± 0%
PoolContention/workers-8-4             1.203Ki ± 0%
PoolContention/workers-16-4            1.203Ki ± 0%
ComponentLifecycle-4                   2.099Mi ± 0%
SourceValidation-4                     1.984Ki ± 0%
RegistryConcurrent-4                   1.133Ki ± 0%
LoaderLoadFromString-4                 2.099Mi ± 0%
geomean                                15.05Ki

                            │ benchmark-results.txt │
                            │       allocs/op       │
InterpreterCreation-4                   15.09k ± 0%
ComponentLoad-4                         17.43k ± 0%
ComponentExecute-4                       25.00 ± 0%
PoolContention/workers-1-4               25.00 ± 0%
PoolContention/workers-2-4               25.00 ± 0%
PoolContention/workers-4-4               25.00 ± 0%
PoolContention/workers-8-4               25.00 ± 0%
PoolContention/workers-16-4              25.00 ± 0%
ComponentLifecycle-4                    17.48k ± 0%
SourceValidation-4                       32.00 ± 0%
RegistryConcurrent-4                     2.000 ± 0%
LoaderLoadFromString-4                  17.47k ± 0%
geomean                                  181.2

pkg: github.com/GoCodeAlone/workflow/middleware
cpu: AMD EPYC 7763 64-Core Processor                
                                  │ baseline-bench.txt │
                                  │       sec/op       │
CircuitBreakerDetection-4                  285.3n ± 6%
CircuitBreakerExecution_Success-4          22.51n ± 0%
CircuitBreakerExecution_Failure-4          64.40n ± 0%
geomean                                    74.50n

                                  │ baseline-bench.txt │
                                  │        B/op        │
CircuitBreakerDetection-4                 144.0 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │
                                  │     allocs/op      │
CircuitBreakerDetection-4                 1.000 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                  │ benchmark-results.txt │
                                  │        sec/op         │
CircuitBreakerDetection-4                     448.8n ± 5%
CircuitBreakerExecution_Success-4             59.79n ± 0%
CircuitBreakerExecution_Failure-4             64.69n ± 0%
geomean                                       120.2n

                                  │ benchmark-results.txt │
                                  │         B/op          │
CircuitBreakerDetection-4                    144.0 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

                                  │ benchmark-results.txt │
                                  │       allocs/op       │
CircuitBreakerDetection-4                    1.000 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
cpu: AMD EPYC 7763 64-Core Processor                
                                 │ baseline-bench.txt │
                                 │       sec/op       │
JQTransform_Simple-4                     876.8n ± 25%
JQTransform_ObjectConstruction-4         1.459µ ±  5%
JQTransform_ArraySelect-4                3.317µ ±  0%
JQTransform_Complex-4                    37.77µ ±  1%
JQTransform_Throughput-4                 1.797µ ±  1%
SSEPublishDelivery-4                     61.79n ±  0%
geomean                                  1.616µ

                                 │ baseline-bench.txt │
                                 │        B/op        │
JQTransform_Simple-4                   1.273Ki ± 0%
JQTransform_ObjectConstruction-4       1.773Ki ± 0%
JQTransform_ArraySelect-4              2.625Ki ± 0%
JQTransform_Complex-4                  16.22Ki ± 0%
JQTransform_Throughput-4               1.984Ki ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │
                                 │     allocs/op      │
JQTransform_Simple-4                     10.00 ± 0%
JQTransform_ObjectConstruction-4         15.00 ± 0%
JQTransform_ArraySelect-4                30.00 ± 0%
JQTransform_Complex-4                    324.0 ± 0%
JQTransform_Throughput-4                 17.00 ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                 │ benchmark-results.txt │
                                 │        sec/op         │
JQTransform_Simple-4                        920.2n ± 27%
JQTransform_ObjectConstruction-4            1.486µ ±  1%
JQTransform_ArraySelect-4                   3.147µ ±  1%
JQTransform_Complex-4                       35.38µ ±  1%
JQTransform_Throughput-4                    1.809µ ±  2%
SSEPublishDelivery-4                        72.97n ±  1%
geomean                                     1.649µ

                                 │ benchmark-results.txt │
                                 │         B/op          │
JQTransform_Simple-4                      1.273Ki ± 0%
JQTransform_ObjectConstruction-4          1.773Ki ± 0%
JQTransform_ArraySelect-4                 2.625Ki ± 0%
JQTransform_Complex-4                     16.22Ki ± 0%
JQTransform_Throughput-4                  1.984Ki ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                 │ benchmark-results.txt │
                                 │       allocs/op       │
JQTransform_Simple-4                        10.00 ± 0%
JQTransform_ObjectConstruction-4            15.00 ± 0%
JQTransform_ArraySelect-4                   30.00 ± 0%
JQTransform_Complex-4                       324.0 ± 0%
JQTransform_Throughput-4                    17.00 ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
cpu: AMD EPYC 7763 64-Core Processor                
                                    │ baseline-bench.txt │
                                    │       sec/op       │
SchemaValidation_Simple-4                    1.111µ ± 5%
SchemaValidation_AllFields-4                 1.674µ ± 3%
SchemaValidation_FormatValidation-4          1.601µ ± 2%
SchemaValidation_ManySchemas-4               1.826µ ± 3%
geomean                                      1.526µ

                                    │ baseline-bench.txt │
                                    │        B/op        │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │
                                    │     allocs/op      │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                    │ benchmark-results.txt │
                                    │        sec/op         │
SchemaValidation_Simple-4                      1.027µ ± 13%
SchemaValidation_AllFields-4                   1.519µ ±  4%
SchemaValidation_FormatValidation-4            1.485µ ±  3%
SchemaValidation_ManySchemas-4                 1.482µ ±  5%
geomean                                        1.361µ

                                    │ benchmark-results.txt │
                                    │         B/op          │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

                                    │ benchmark-results.txt │
                                    │       allocs/op       │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ baseline-bench.txt │
                                   │       sec/op       │
EventStoreAppend_InMemory-4                1.203µ ± 44%
EventStoreAppend_SQLite-4                  2.274m ±  9%
GetTimeline_InMemory/events-10-4           13.83µ ±  3%
GetTimeline_InMemory/events-50-4           69.67µ ± 14%
GetTimeline_InMemory/events-100-4          124.3µ ±  1%
GetTimeline_InMemory/events-500-4          640.5µ ±  1%
GetTimeline_InMemory/events-1000-4         1.301m ±  1%
GetTimeline_SQLite/events-10-4             107.9µ ±  1%
GetTimeline_SQLite/events-50-4             249.4µ ±  0%
GetTimeline_SQLite/events-100-4            422.5µ ±  1%
GetTimeline_SQLite/events-500-4            1.794m ±  2%
GetTimeline_SQLite/events-1000-4           3.488m ±  0%
geomean                                    227.7µ

                                   │ baseline-bench.txt │
                                   │        B/op        │
EventStoreAppend_InMemory-4                  813.0 ± 7%
EventStoreAppend_SQLite-4                  1.981Ki ± 3%
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%
geomean                                    67.50Ki

                                   │ baseline-bench.txt │
                                   │     allocs/op      │
EventStoreAppend_InMemory-4                  7.000 ± 0%
EventStoreAppend_SQLite-4                    53.00 ± 2%
GetTimeline_InMemory/events-10-4             125.0 ± 0%
GetTimeline_InMemory/events-50-4             653.0 ± 0%
GetTimeline_InMemory/events-100-4           1.306k ± 0%
GetTimeline_InMemory/events-500-4           6.514k ± 0%
GetTimeline_InMemory/events-1000-4          13.02k ± 0%
GetTimeline_SQLite/events-10-4               382.0 ± 0%
GetTimeline_SQLite/events-50-4              1.852k ± 0%
GetTimeline_SQLite/events-100-4             3.681k ± 0%
GetTimeline_SQLite/events-500-4             18.54k ± 0%
GetTimeline_SQLite/events-1000-4            37.29k ± 0%
geomean                                     1.162k

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                   │ benchmark-results.txt │
                                   │        sec/op         │
EventStoreAppend_InMemory-4                   1.169µ ± 19%
EventStoreAppend_SQLite-4                     947.6µ ±  4%
GetTimeline_InMemory/events-10-4              13.63µ ±  3%
GetTimeline_InMemory/events-50-4              74.39µ ±  3%
GetTimeline_InMemory/events-100-4             117.6µ ± 34%
GetTimeline_InMemory/events-500-4             601.9µ ±  1%
GetTimeline_InMemory/events-1000-4            1.233m ±  1%
GetTimeline_SQLite/events-10-4                82.40µ ±  1%
GetTimeline_SQLite/events-50-4                234.2µ ±  2%
GetTimeline_SQLite/events-100-4               425.3µ ±  1%
GetTimeline_SQLite/events-500-4               1.904m ±  1%
GetTimeline_SQLite/events-1000-4              3.697m ±  2%
geomean                                       205.5µ

                                   │ benchmark-results.txt │
                                   │         B/op          │
EventStoreAppend_InMemory-4                     818.0 ± 9%
EventStoreAppend_SQLite-4                     1.984Ki ± 2%
GetTimeline_InMemory/events-10-4              7.953Ki ± 0%
GetTimeline_InMemory/events-50-4              46.62Ki ± 0%
GetTimeline_InMemory/events-100-4             94.48Ki ± 0%
GetTimeline_InMemory/events-500-4             472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4            944.3Ki ± 0%
GetTimeline_SQLite/events-10-4                16.74Ki ± 0%
GetTimeline_SQLite/events-50-4                87.14Ki ± 0%
GetTimeline_SQLite/events-100-4               175.4Ki ± 0%
GetTimeline_SQLite/events-500-4               846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4              1.639Mi ± 0%
geomean                                       67.54Ki

                                   │ benchmark-results.txt │
                                   │       allocs/op       │
EventStoreAppend_InMemory-4                     7.000 ± 0%
EventStoreAppend_SQLite-4                       53.00 ± 0%
GetTimeline_InMemory/events-10-4                125.0 ± 0%
GetTimeline_InMemory/events-50-4                653.0 ± 0%
GetTimeline_InMemory/events-100-4              1.306k ± 0%
GetTimeline_InMemory/events-500-4              6.514k ± 0%
GetTimeline_InMemory/events-1000-4             13.02k ± 0%
GetTimeline_SQLite/events-10-4                  382.0 ± 0%
GetTimeline_SQLite/events-50-4                 1.852k ± 0%
GetTimeline_SQLite/events-100-4                3.681k ± 0%
GetTimeline_SQLite/events-500-4                18.54k ± 0%
GetTimeline_SQLite/events-1000-4               37.29k ± 0%
geomean                                        1.162k

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

@intel352
Copy link
Contributor

@copilot apply changes based on the comments in this thread

Also ensure tests are passing

…roker required, fix test assertion, decrypt algorithm validation

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot apply changes based on the comments in this thread

Also ensure tests are passing

Applied all reviewer feedback in commit 1343597:

  • resolveEncryptionKey now requires env-var format ($VAR or ${VAR}) — literal key strings return a descriptive error, preventing operators from treating non-secret identifiers (e.g. KMS ARNs) as key material
  • provider and algorithm validated at factory time — unsupported values (kms, ChaCha20-Poly1305, etc.) return a config error immediately instead of being silently ignored
  • Encryption without a broker/provider is now a factory-time error — prevents the EventBus path from publishing ciphertext without the encryption metadata extensions (encrypteddek/encryptedfields/keyid)
  • step.event_decrypt validates the encryption extension — an unsupported algorithm returns a clear error rather than attempting decryption
  • Fixed TestEventPublishStep_EncryptionConfig_EnvVarKey — now correctly reads from envelope["data"].(map[string]any)["phone"] since encryption always produces a wrapped envelope; also asserts the field is non-nil (confirming encryption ran)
  • Added 5 new tests covering: literal key rejection, unsupported provider, unsupported algorithm (publish), unsupported algorithm (decrypt), and missing broker with encryption

Copilot AI requested a review from intel352 March 12, 2026 16:43
@intel352
Copy link
Contributor

@copilot fix failing tests

…fctl type registry

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot fix failing tests

Fixed in commit 56ee8ab. Three tests were failing because step.event_decrypt wasn't registered in the test/documentation infrastructure:

  • TestStepFactories (plugins/pipelinesteps) — added step.event_decrypt to expectedSteps
  • TestDocumentationCoverage (plugins/all) — added step.event_decrypt row to DOCUMENTATION.md
  • TestKnownStepTypesCoverAllPlugins (cmd/wfctl) — added step.event_decrypt entry to KnownStepTypes() in type_registry.go

All tests pass now.

@intel352 intel352 merged commit 89e6f9e into main Mar 12, 2026
11 checks passed
@intel352 intel352 deleted the copilot/feature-field-level-encryption branch March 12, 2026 17:22
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.

Feature: field-level encryption support for step.event_publish

3 participants