Skip to content

adapters/nats: optional NATS bridge for ai/verify barrier decisions - #385

Merged
gerardrecinto merged 6 commits into
masterfrom
adapters-nats-verify-bridge
Sep 21, 2026
Merged

gerardrecinto merged 6 commits into
masterfrom
adapters-nats-verify-bridge

Conversation

@gerardrecinto

Copy link
Copy Markdown
Collaborator

This is entirely opt-in and doesn't touch joltrin's embedded core, the B-Tree/storage hot path, or ai/verify itself. Happy to adjust or close this if it isn't a direction you want.

I went looking for a genuine, honest way to let an external system observe joltrin's internal events over NATS, without contradicting the project's whole embedded-only pitch. ai/verify (the runbook safety barrier described in docs/MCP_A2A_AND_VERIFICATION_ENGINE.md) turned out to be a clean fit: it's a synchronous, dependency-free library, CheckSafety/CheckAndCommit/CheckAndCommitIdempotent just return their decision directly to the caller, with no event or hook mechanism of its own. Agent memory checkpointing, the other place I looked, is a direct B-Tree write with no comparable seam, so I left that alone rather than force something into the storage path.

What this adds: adapters/nats, its own Go module under go.work (same pattern as adapters/redis and adapters/cassandra). VerifyBridge wraps a *verify.Workflow and publishes a small JSON BarrierDecision event to a NATS subject after each barrier check, for a team that already runs NATS elsewhere and wants to observe these decisions from another service. It calls straight through to the wrapped workflow and returns its exact result unchanged; a publish failure can never change or block the barrier's own decision. Nothing in ai/verify, tools/mcpserver, or tools/a2aagent was modified, and nothing here reads or writes the B-Tree.

Also included: tests against a real in-process NATS server (nats-server/v2), so they need nothing listening externally, either locally or in CI; a runnable example (examples/verify_barrier_nats) that replays examples/verify_barrier's blocked-then-allowed sequence through the bridge with a subscriber attached; a short addendum to docs/MCP_A2A_AND_VERIFICATION_ENGINE.md; and adapters/nats added to the CI workflow's per-module build/vet/test loop alongside the other workspace modules.

Ran locally before pushing: gofmt -l (clean), go build/go vet across the root module with the same exclusions CI uses, and the same for-loop CI runs over search, jsondb, incfs, adapters/cassandra, adapters/redis, and adapters/nats, all passing, including go test -race for the new module.

VerifyBridge wraps a *verify.Workflow and publishes a BarrierDecision
event to a NATS subject after each CheckSafety/CheckAndCommit/
CheckAndCommitIdempotent call, for a team that already runs NATS and
wants to observe barrier decisions from another service without
polling. It calls straight through to the wrapped workflow and returns
its exact result; a publish failure never changes or blocks the
barrier's own decision. ai/verify itself is untouched, and nothing
here reads or writes the B-Tree.

Own Go module under go.work, same pattern as adapters/redis and
adapters/cassandra. Tests start an in-process NATS server
(nats-server/v2) so they need no external service listening.
Added it to the same for-loop that already builds, vets, and tests
search, jsondb, incfs, adapters/cassandra, and adapters/redis, each a
separate module under go.work that go list ./... from the root never
reaches on its own.
Runs the same blocked-then-allowed drop_prod_db sequence as
examples/verify_barrier, through an adapters/nats.VerifyBridge instead
of calling *verify.Workflow directly, with a second goroutine
subscribed to the published events standing in for an external
observer. Needs a NATS server at nats://127.0.0.1:4222 to run; prints
a plain message and exits if one isn't reachable.
Short addendum to docs/MCP_A2A_AND_VERIFICATION_ENGINE.md explaining
why ai/verify has no event mechanism of its own, what the bridge adds
instead, and why it's opt-in and doesn't touch storage.
@github-actions

Copy link
Copy Markdown

Gemini PR Review

The diff introduces a new adapters/nats Go module that provides an optional observability bridge for ai/verify barrier decisions. The changes include the core logic, comprehensive tests, and updated documentation across the repository.

Code quality and readability

  • Excellent documentation: The doc.go file, struct comments, and method comments are thorough and clearly explain the purpose, design principles, and behavior of the bridge, including its opt-in nature and the handling of publish failures.
  • Clear design: The VerifyBridge acts as a decorator, calling through to the underlying verify.Workflow methods and performing NATS publishing as a side effect. This separation of concerns is well-implemented.
  • Idiomatic Go: Uses t.Helper() and t.Cleanup in tests for robust resource management. The OnPublishError method uses chaining idiomatically.
  • JSON marshalling: BarrierDecision uses omitempty for optional fields, which is good practice for reducing payload size.
  • Test utilities: The startTestServer and collectDecisions helper functions in the test file are well-written and make the tests clean and effective.

Potential bugs and correctness issues

  • Correct error extraction: The publish method correctly uses errors.As to distinguish between a verify.Violation (which provides Rule and MissingState) and other errors, ensuring accurate event data.
  • Idempotency handling: The CheckAndCommitIdempotent method correctly passes the replayed status to the publish method, and BarrierDecision includes the Replayed field, which is correctly tested.
  • Marshal error handling: A JSON marshal error is explicitly considered a bug in the BarrierDecision struct itself and is reported via onPublishError if set, preventing silent failures of a fundamental operation.

Missed edge cases

  • Publish failure resilience: The design explicitly states and tests that NATS publish failures do not alter the outcome of the underlying barrier decision (TestVerifyBridge_PublishFailureDoesNotChangeDecision). This is a crucial and correctly handled edge case for an observability component.
  • Malformed messages: The Subscribe helper function deliberately drops messages that fail to unmarshal into a BarrierDecision, preventing a single bad message from disrupting the entire subscriber. This behavior is documented as a design choice.
  • Empty workflow name: The SubjectFor function correctly falls back to DefaultSubject for an empty workflow name.

Performance considerations

  • Fire-and-forget publishing: The NATS Publish call is asynchronous by default in nats.go (no Flush is called). This ensures that the overhead of JSON marshalling and the network send does not block the core verify workflow. This is an appropriate performance trade-off for an observability side-effect.
  • Minimal overhead: The operations (struct instantiation, time.Now().UTC(), errors.As, JSON marshalling, NATS publish) are generally efficient and should not introduce significant latency to the critical barrier path.

The diff introduces a well-designed, robust, and thoroughly tested NATS observability bridge without compromising the core ai/verify logic. The code quality, correctness, and attention to edge cases are excellent.

…lves, and skip the image-scan sarif upload when the scan never ran
@github-actions

Copy link
Copy Markdown

Gemini PR Review

This pull request introduces a new adapters/nats module to provide an opt-in NATS observability bridge for ai/verify barrier decisions. The changes also include necessary updates to CI, Dockerfile, and documentation.

Code quality and readability

  • Excellent documentation: The new doc.go file, the Addendum in MCP_A2A_AND_VERIFICATION_ENGINE.md, and inline comments clearly explain the purpose, design principles, and usage of the NATS bridge. This is particularly valuable for an opt-in component that doesn't affect the core behavior.
  • Clear separation of concerns: The VerifyBridge acts as a pure decorator, ensuring that the NATS publishing is a side effect and does not alter the original ai/verify.Workflow's behavior or return values.
  • Robust error handling: The publish method thoughtfully handles errors during JSON marshaling (unlikely for a fixed struct, but still covered) and NATS publishing. Critically, these errors are passed to an optional onPublishError handler and do not block or change the barrier's decision.
  • Thorough testing: The bridge_test.go file is comprehensive, using an embedded NATS server for self-contained, fast tests. It covers successful publishes, blocked decisions (precondition and safety rule violations), idempotency, and the crucial scenario where NATS publish failures do not affect the barrier's outcome.
  • Clean JSON marshaling: The BarrierDecision struct uses json:"field,omitempty" correctly, ensuring compact JSON output when fields are empty.
  • Good helper functions: SubjectFor and the Subscribe convenience wrapper (with its explicit handling of malformed messages) are well-designed.

Potential bugs and correctness issues

  • No obvious bugs. The logic for constructing BarrierDecision and extracting details from *verify.Violation using errors.As is correct. The idempotent call handling is also correctly reflected in the Replayed field.

Missed edge cases

  • The design explicitly considers and addresses several edge cases:
    • NATS publish failures not affecting the primary barrier decision.
    • Handling different types of barrier errors (precondition vs. SafetyRule violations) for detailed event reporting.
    • The JSON wire format for BarrierDecision is pinned and tested.
    • Malformed messages in the Subscribe helper are deliberately dropped to maintain subscriber resilience.

Performance considerations

  • The overhead introduced by marshaling the BarrierDecision struct to JSON and publishing it to NATS is expected for an observability component. Given that barrier decisions are not typically a high-frequency operation, this overhead is unlikely to be a bottleneck. The design ensures that this overhead does not impact the critical path of the barrier's decision logic.

… doesn't reverse joltrin's embedded thesis, it never touches the hot path
@github-actions

Copy link
Copy Markdown

Gemini PR Review

This pull request introduces an optional NATS bridge for observing ai/verify barrier decisions, along with necessary updates to CI, Docker, and documentation.

Overall, the diff looks fine and is a well-executed feature addition.

Code quality and readability

  • Excellent documentation: The new adapters/nats/doc.go and the addendum in docs/MCP_A2A_AND_VERIFICATION_ENGINE.md provide a clear and thorough explanation of the package's purpose, design philosophy, and how it aligns with the project's embedded-first approach.
  • Clear structure: The BarrierDecision struct is well-defined with appropriate json tags and comments. The VerifyBridge methods clearly delegate to the underlying verify.Workflow and then perform the NATS publish as a side effect.
  • Testability: The bridge_test.go includes a robust startTestServer helper, enabling comprehensive, isolated integration tests without external NATS dependencies.

Potential bugs and correctness issues

  • Publish failures isolation: The design explicitly prevents NATS publish errors from affecting the core barrier decision logic. The publish method handles errors via an onPublishError callback and never returns an error to the caller of CheckSafety/CheckAndCommit methods, which is critical for correctness. This is also thoroughly tested in TestVerifyBridge_PublishFailureDoesNotChangeDecision.
  • JSON serialization robustness: The BarrierDecision struct uses omitempty tags correctly, and the publish function includes error handling for json.Marshal failures, indicating that such an error would represent an internal bug in the struct definition.
  • Error detail extraction: The use of errors.As(err, &violation) ensures that Rule and MissingState details from verify.Violation are correctly extracted and included in the published events for both precondition and safety rule violations.

Missed edge cases

  • Malformed messages in subscriber: The adapters/nats/subscribe.go explicitly states and justifies the decision to drop malformed JSON messages rather than failing the subscriber, which is a reasonable approach for an observability stream.
  • Empty workflow name: SubjectFor correctly handles an empty workflow string by returning DefaultSubject.

Performance considerations

  • Asynchronous side effect: The NATS publish operation occurs after the core verify.Workflow logic has completed and returned its decision. The nats.go Publish method is asynchronous, queuing the message for sending, which means it introduces minimal blocking latency to the barrier check itself. This aligns with the "fire-and-forget" principle stated in the documentation, ensuring the NATS bridge does not become a hot path performance bottleneck for the core application.

@gerardrecinto
gerardrecinto merged commit e4ee0d6 into master Sep 21, 2026
26 checks passed
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.

1 participant