conformance: decode schema 1 and schema 3 in one binary - #70
Open
bgm-malbeclabs wants to merge 15 commits into
Open
conformance: decode schema 1 and schema 3 in one binary#70bgm-malbeclabs wants to merge 15 commits into
bgm-malbeclabs wants to merge 15 commits into
Conversation
Continuous subscriber-side validation of every feed registered onchain, replacing the hand-listed per-venue conformance deploys. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The publisher-side suites and dz-conformance are two layers, not two implementations of one. Nothing is deleted; the pcap handoff phoenix already built becomes the standard. The feed-to-ports table moves out of this public repository into infra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hyperliquid's name is a value and a subscription filter key in doublezero-edge-connect's public PROTOCOL.md, so a rename breaks subscriber filters silently. Binance is cheap to rename but already disclosed onchain. Codenames apply at registration, not retroactively. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Splits phase A: the decoder ships on its own and deletes the per-venue pin, and fleet mode cannot start until it does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… constraint Withdraws the draft's assumption that the multicast recorder hosts were spare capacity. Every dz_conformance host is already a recorder host, and the single-queue GRE loss path is documented and observed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…i-first The operator ask is a dashboard and an owner, not a checker. Records the publisher/receiver/runbook panels, reconciles the channel-instance tuple, and moves the conformance crate out of the hyperliquid repo per its author. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary of Changes
Makes one
dz-conformancebinary decode schema 1 and schema 3, so the per-venue version pin can be deleted.wire.SupportedSchemas/SchemaSupportedreplaceExpectedSchemaVersion: a feed now has a set of accepted versions (midpoint{1}, everything else{1, 3}). Schema 2 stays rejected — no publisher ever deployed it, and a layout nobody has seen on the wire is a guess.engine/instrdef.goholds the(feed, schema)→InstrumentDefinitionoffset table. That message is the only part of the wire format whose layout moves between the supported versions, so one table covers multi-schema support for the whole engine.expectedMsgLenand the field accessors read it; no offset literal survives outside it.engine.beginFrame's downgrade gate changes fromschemaVersion > <the one expected version>to membership. Ordering was a proxy for "can we decode this" that only worked for a one-element set — and it was already wrong for schema 2, which is unsupported but lower than 3, so it graded such a stream at full severity through schema-3 offsets.beginFrame's own comment named multi-version decode as the proper fix; this is it.VERSIONING.mdrecords the exception this creates. The spec says a decoder MUST reject aSchema Versionit was not built for; this validator now does not, deliberately and for itself only. An undocumented exception to a MUST is how the rule quietly stops meaning anything.Why.
dz_conformance_versionis pinned per venue today because a build reads one schema. Hyperliquid emits 1, Kalshi emits 3, and the wrong pin mis-sizesInstrumentDefinitionby 50 bytes and firesMSG.LENGTH_PER_TYPE— a must rule — on every definition datagram. The pin is not configuration; it is the same knowledge held both on the wire and in a group_vars string a human has to keep equal to it.malbeclabs/infra's owndz_conformance.ymlcomment already documents where that breaks: a fourth Kalshi metro added the waydubwas inherits a pin with no feeds.Schema-1 offsets came from git tag
top-of-book/v1.0.0, not from any current spec file — the 80-byte layout was retired by the 2.0.0 and 3.0.0 releases. The code comment says so, because the source of truth for half that table is a tag rather than HEAD.The branch also carries the design spec (
docs/superpowers/specs/2026-09-09-fleet-conformance-design.md) this implements §2 of, and its plan. Happy to split those into a separate PR if you would rather review them apart from the code.Spec Impact
Magicor size is altered. NoSchema Versionbyte is bumped.PATCH(editorial) forVERSIONING.md— the new subsection describes an existing tool and clarifies the scope of a rule without changing required behaviour, which is the class table's own definition of editorial.tools/conformancechange is tool behaviour, not specification, and takes aMINORrelease on its ownconformance/<version>tag line: it adds accepted versions and removes none.Review Notes
Not in this PR, and deliberately. The per-venue pins in
malbeclabs/infraare what this unblocks, but deleting them means cutting a release and rolling production, which belongs in that repo with a human driving it. No release should be cut from any commit before37be4a0— between the first commit and that one, the decoder accepts schema 1 while still reading schema-3 offsets, which grades hyperliquid worse than today.Verify against a real capture before releasing. Replay one hyperliquid (schema 1) and one Kalshi (schema 3) capture through the new binary and diff the JSON reports against the two pinned binaries they replace. A new violation there is a finding to investigate, not a regression to suppress — it may be a real publisher fault the wrong pin was masking.
Known gaps, all judged non-blocking by review:
core/ruledoc.go's accepted-set text andwire.SupportedSchemascan drift; guarded by a comment naming the other, not a compile-time check. Packageenginealready imports bothcoreandwire, so a test asserting the rule text names every entry needs no new import edge.top-of-book/spec.md,midpoint/spec.md,market-by-price/spec.md,README.md, andmarket-by-order/spec.md:798("A subscriber MUST reject…") — which bites, becausedz-conformance's README calls it "a conformance subscriber". Left alone here: each spec is independently versioned, so amending them is five PATCH releases and a call for their owners.wire/wirebuild/build.gotakessupported[len-1]as "highest", an unstated ordering contract onSupportedSchemas.One behaviour change beyond the schema work, flagged because it is easy to miss in the diff:
refdata.gonow skips anInstrumentDefinitionwhose body is too short instead of feeding zeros intoonInstrumentDef. That removes cascaded falseREFDATA.STALE_SEQ_TAG_AFTER_BUMPfindings;MSG.LENGTH_PER_TYPEstill fires on the short message.MBP
Price Boundis deliberately still unread. The layout table has a valid offset for it, but the pre-existingswitchsent MBP to itsdefault:arm, so reading it now would be a live grading change smuggled into a schema change. Review confirmed it is inert either way today — MBP's payloads are0x40–0x42and reach none of the three call sites that consumepriceBound.Testing Verification
expectedMsgLenat schema 3 returns the same lengths,instrDefAllFieldsreturns the same four values the oldswitchdid for every feed,beginFrameclassifies schema-3 non-midpoint and midpoint-at-3 exactly as>did, andwirebuild's default resolves to the same version. Schema 3 is what every production venue except hyperliquid emits.TestSchema1TOBStreamRaisesNoMustViolationruns two hand-built schema-1 frames through the realwire.Decode→engine.Process/Flush/EndRunpath and asserts nomustviolation. Proven non-vacuous by mutation: forcing the schema-1 lookup to return the schema-3 layout fails it withMSG.LENGTH_PER_TYPE type 0x02: length 80, expected 130— the exact production symptom the two pinned builds exist to avoid.ManifestSeqandPriceBoundtogether (so the 50-byte delta test still passes) fails 18+ tests.top-of-book/v1.0.0by two reviewers, agreeing with the table and with the test fixture — four derivations of a layout that exists in no current spec file.TestInstrDefLayoutManifestSeqIsLastpins the ordering invariant the bounds-check discards rely on, enumerating rows throughinstrDefLayoutForitself rather than a parallel list, with a floor assertion so an empty sweep fails loudly instead of passing vacuously.TestExpectedMsgLenIsSchemaAwaresweeps the wholeuint8type space per non-midpoint feed rather than a hand-listed set, so a type that becomes schema-dependent later cannot slip through.TestNonconformantMBPCapturegolden counts unchanged at every step. The bundled capture is 2001/2001 schema-1 frames and now decodes through correct offsets rather than mismatched ones.go build,go vet,gofmt -landgo test -count=1 ./...clean across all seven packages, run with the build cache cleared.🤖 Generated with Claude Code