Skip to content

[Hackathon] wire-compat: versioned comms layer with forward/backward …#18

Open
mihiragentic wants to merge 1 commit into
projnanda:mainfrom
mihiragentic:hackathon/wire-compat-comms-versioning
Open

[Hackathon] wire-compat: versioned comms layer with forward/backward …#18
mihiragentic wants to merge 1 commit into
projnanda:mainfrom
mihiragentic:hackathon/wire-compat-comms-versioning

Conversation

@mihiragentic

@mihiragentic mihiragentic commented Jun 5, 2026

Copy link
Copy Markdown

…compat

Problem #1 (comms): the default nest_native envelope carries no schema version, so across NEST builds a newer field is silently dropped and a breaking-major message is silently mis-decoded.

This adds:

  • comms/versioned.py: a VersionedComms plugin that stamps an explicit SemVer schema_version + kind on every envelope, preserves unknown fields from newer-minor peers (re-emitting them on round-trip), and rejects unknown majors with a typed UnsupportedSchemaError instead of mis-decoding.
  • Two adversarial validators (comms_reject_unknown_major, comms_no_silent_drop) that FAIL against nest_native and PASS against versioned, encoded independently of any plugin.
  • A comms_versioning scenario + agents (mixed v1/v2 peers + an auditor) and scenarios/comms_versioning.yaml demonstrating it.
  • Property-based and end-to-end tests; deterministic under seeds 42/7/1337.

make ci-local: all 5 checks pass (365 tests, 0 type errors, lint+format clean).

Summary by Sourcery

Introduce a versioned communications plugin and scenario to enforce forward/backward wire compatibility and validate comms behaviour.

New Features:

  • Add a VersionedComms plugin that stamps schema versions on envelopes, preserves unknown minor-version fields, and rejects unsupported major versions.
  • Add a comms_versioning scenario with mixed-version peers and an auditor to exercise comms plugins against versioning rules.
  • Register the versioned comms plugin and comms_versioning scenario in the core plugin and scenario registries, including a new YAML scenario definition.

Enhancements:

  • Add adversarial validators that ensure unknown-major envelopes are rejected and unknown fields from newer minors are not silently dropped.
  • Extend validator tests to cover the new comms validators and verify deterministic traces across seeds for the comms_versioning scenario.

Tests:

  • Add property-based and compatibility tests for the VersionedComms plugin, including round-trip stability, unknown-field preservation, and rejection semantics.
  • Add end-to-end simulator tests that compare behaviour of nest_native vs versioned comms using the comms_versioning scenario.

…compat

Problem projnanda#1 (comms): the default nest_native envelope carries no schema
version, so across NEST builds a newer field is silently dropped and a
breaking-major message is silently mis-decoded.

This adds:
- comms/versioned.py: a VersionedComms plugin that stamps an explicit SemVer
  schema_version + kind on every envelope, preserves unknown fields from
  newer-minor peers (re-emitting them on round-trip), and rejects unknown
  majors with a typed UnsupportedSchemaError instead of mis-decoding.
- Two adversarial validators (comms_reject_unknown_major,
  comms_no_silent_drop) that FAIL against nest_native and PASS against
  versioned, encoded independently of any plugin.
- A comms_versioning scenario + agents (mixed v1/v2 peers + an auditor) and
  scenarios/comms_versioning.yaml demonstrating it.
- Property-based and end-to-end tests; deterministic under seeds 42/7/1337.

make ci-local: all 5 checks pass (365 tests, 0 type errors, lint+format clean).

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

sourcery-ai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new versioned communications plugin and adversarial validation scenario to enforce forward/backward-compatible wire envelopes, wires it into the plugin registry and scenarios, and tests both the plugin and validators end-to-end.

Sequence diagram for the comms_versioning scenario with VersionedComms

sequenceDiagram
    actor PeerAgent
    participant AuditorAgent
    participant VersionedComms

    PeerAgent->>AuditorAgent: ctx.send(_envelope v1.1)
    AuditorAgent->>VersionedComms: deserialize(payload)
    alt [major == SCHEMA_MAJOR]
        VersionedComms-->>AuditorAgent: Message(metadata._unknown preserved)
        AuditorAgent->>PeerAgent: ctx.send(ack:<id>:accepted:<preserved_fields>)
    else [major > SCHEMA_MAJOR]
        VersionedComms-->>AuditorAgent: UnsupportedSchemaError
        AuditorAgent->>PeerAgent: ctx.send(ack:<id>:rejected_major:)
    end
Loading

File-Level Changes

Change Details Files
Introduce adversarial comms validators that enforce rejection of unknown-major envelopes and preservation of unknown fields from newer-minor peers, and register them under a new comms_versioning validator suite.
  • Add helpers to parse comms envelopes from trace events, extract schema majors, and collect on-the-wire envelope and ack metadata.
  • Implement validate_comms_reject_unknown_major to assert that envelopes with higher major versions are explicitly rejected with appropriate ack status.
  • Implement validate_comms_no_silent_drop to assert that unknown top-level fields for same-major envelopes are preserved and reported via acks.
  • Register the new validators under the comms_versioning scenario key and include it in the global VALIDATORS registry.
packages/nest-core/nest_core/validators.py
packages/nest-core/tests/test_validators.py
Register and implement a VersionedComms plugin that stamps SemVer schema_version and kind onto envelopes, preserves unknown fields from newer minors, and rejects unknown majors with a typed error.
  • Extend the plugin registry to expose the new comms versioned implementation under the built-in plugins map.
  • Define schema version constants, known envelope fields, and reserved metadata keys to control wire and in-model representations.
  • Implement UnsupportedSchemaError and a SemVer-major parser to guard deserialization against unknown or malformed majors.
  • Implement VersionedComms.serialize to generate JSON envelopes with schema_version, kind, and re-emitted unknown fields while maintaining deterministic, sorted-key output.
  • Implement VersionedComms.deserialize to accept same-major envelopes, treat missing schema_version as 1.0, capture unknown fields into metadata['_unknown'], and raise UnsupportedSchemaError for future majors or malformed envelopes.
  • Provide basic send/advertise/discover wrappers to integrate with transports and registries while conforming to the CommsProtocol interface.
packages/nest-core/nest_core/plugins.py
packages/nest-plugins-reference/nest_plugins_reference/comms/versioned.py
Add a comms_versioning scenario and agents that inject mixed-version envelopes directly onto the wire and an auditor that reports decoder behaviour via acks, then wire this scenario into the scenario loader.
  • Implement helper functions to build canonical JSON envelopes with optional extra top-level fields and to recover best-effort ids from raw bytes.
  • Implement PeerAgent that schedules itself, emits 1.0-only envelopes for v1 peers, and both 1.1-with-unknown-field and 2.0-breaking envelopes for v2 peers.
  • Implement AuditorAgent that uses the configured comms plugin to deserialize envelopes and emits structured ack::: messages.
  • Implement comms_versioning_factory to create one auditor plus a mix of v1/v2 peers driven by ScenarioConfig and selected comms plugin.
  • Register the comms_versioning scenario in the built-in scenario loader and add a YAML scenario file with deterministic configuration, metrics, and trace output path.
packages/nest-core/nest_core/scenarios.py
packages/nest-core/nest_core/scenarios_builtin/comms_versioning.py
scenarios/comms_versioning.yaml
Add property-based, compatibility, rejection, and API-fit tests for the VersionedComms plugin and adversarial comms_versioning validators, including deterministic simulator runs under multiple seeds.
  • Define Hypothesis strategies to generate canonical envelopes, including random unknown fields, and use them to assert round-trip stability and unknown-field preservation.
  • Add unit tests for VersionedComms compatibility behaviours (higher minor acceptance, missing version handling) and strict rejection of higher or malformed majors and malformed envelopes.
  • Verify VersionedComms satisfies the CommsProtocol and is resolvable from the PluginRegistry as the comms versioned implementation.
  • Add synthetic-trace tests for validate_comms_reject_unknown_major and validate_comms_no_silent_drop covering pass/fail cases and edge conditions like dropped messages.
  • Add end-to-end tests that run the comms_versioning scenario with both versioned and nest_native comms, asserting expected validator outcomes and deterministic traces under seeds 42/7/1337.
packages/nest-plugins-reference/tests/test_comms_versioned.py
packages/nest-core/tests/test_comms_versioning.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/nest-core/nest_core/validators.py" line_range="991" />
<code_context>
+        if len(parts) < 3:
+            continue
+        mid, status = parts[1], parts[2]
+        preserved = {f for f in parts[3].split(",") if f} if len(parts) > 3 else set[str]()
+        acks[mid] = (status, preserved)
+    return acks
</code_context>
<issue_to_address>
**issue (bug_risk):** The fallback `preserved` set uses `set[str]()` which will raise at runtime.

On Python 3.9+, `set[str]` is a `types.GenericAlias`, so calling `set[str]()` raises a `TypeError`, breaking ack parsing when the message has fewer than four segments.

Use a plain empty set for the fallback:

```python
preserved = {f for f in parts[3].split(",") if f} if len(parts) > 3 else set()
```

Since you then treat `preserved` as a regular `set[str]` of strings, this change does not affect typing in practice.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

if len(parts) < 3:
continue
mid, status = parts[1], parts[2]
preserved = {f for f in parts[3].split(",") if f} if len(parts) > 3 else set[str]()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The fallback preserved set uses set[str]() which will raise at runtime.

On Python 3.9+, set[str] is a types.GenericAlias, so calling set[str]() raises a TypeError, breaking ack parsing when the message has fewer than four segments.

Use a plain empty set for the fallback:

preserved = {f for f in parts[3].split(",") if f} if len(parts) > 3 else set()

Since you then treat preserved as a regular set[str] of strings, this change does not affect typing in practice.

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.

2 participants