feat(schema): v2.0 schema foundation + Go validator (Phase 1 slice 1)#12
Merged
Conversation
Land the structural foundation Phase 1 needs before any SDK rewrite:
the v2.0 wide-event JSON Schema, the v1.1 bridge schema, six golden
fixtures, and a coexisting pkg/event/v2 Go package with types, schema-
backed Validate, structural parity test, and a v1.1 bridge accept test.
The existing v1.1 pkg/event stays intact during the bridge window.
- docs/schema/v2.0.json — JSON Schema (draft 2020-12) for the v2 wide
event. Encodes the §3.2 invariants: status, schema_version, event_id,
ts_start, ts_end, kind, service, env, trace_id all required;
status ∈ {error, timeout, partial, aborted} ⇒ anchor required;
step.downstream is present ⇒ step.span_id required.
- docs/schema/v1.1.json — loose bridge schema accepting the current
v1.x shape; additive only.
- testdata/fixtures/v2/{ok-simple, error-payment-cascade, timeout-
watchdog, aborted-cancel, suppressed-healthcheck, error-panic}.json
— six golden scenarios covering every status and every reserved
lifecycle code (WAYLOG_TIMEOUT/ABORTED/PANIC + PMT_502).
- pkg/event/v2 — new package eventv2: schema 2.0 types
(Event/Anchor/Step/Downstream/StepError/Log/ErrorRef), Status and
reserved-code constants, Validate(*Event) and ValidateFile against
the v2.0 schema. SpanID and ParentSpanID intentionally lack
omitempty: SDK-emitted events always carry both as string fields per
§3.2 (parent_span_id empty for root-of-trace). Coexists with v1.1
pkg/event.
- pkg/go.mod — adds github.com/santhosh-tekuri/jsonschema/v5 v5.3.1.
Tests:
- TestFixturesValidateAgainstSchema — every fixture validates against
docs/schema/v2.0.json.
- TestFixtureRoundTripStructural — for each fixture, unmarshal into
Event, marshal back, mask runtime-generated fields with non-empty
sentinels (so explicit empty-string parent_span_id survives
normalization), normalize semantic-empty values, structurally compare.
Catches JSON-tag drift, omitempty on must-have-fields, and silent
field drops on the SDK-contract-required identity surface.
- TestV11BridgeSchemaAcceptsCurrentV1Event — proves the v1.1 schema is
in lockstep with the actual pkg/event.WideEvent shape during the
bridge window.
- TestV11BridgeSchemaRejectsMissingTraceID — sanity check that the
bridge schema is not vacuous.
- TestV20SchemaRequiresStatus — locks in that an event without status
is rejected.
- TestEventRoundTrip, TestStatusConstants — basic shape sanity.
Verification:
- cd pkg && go test ./event/v2/... -v → 17/17 PASS
- go test ./... at repo root → all packages green; v1.1 pkg/event
untouched.
- go build ./... at repo root → clean.
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
First slice of the v2 plan in
docs/v2-plan.md— the schema foundation that the Go SDK rewrite, TS SDK, and runtimeingest validator will all sit on. No public SDK surface or runtime behavior changes yet; the v1.1 surface is untouched
and the repo still builds and tests green.
Maps to Tasks 1–4 of
docs/plans/2026-04-25-v2-phase-1a-go-sdk.md. Tasks 5–24 (SDK core, middleware, deliveryworker, perf gate) land in subsequent PRs.
What's in this PR
Schema artifacts
docs/schema/v2.0.json— JSON Schema (draft 2020-12) for the v2 wide event. Encodes the v2-plan §3.2 invariants:schema_version,event_id,ts_start,ts_end,kind,service,env,trace_id,statusstatus ∈ {error, timeout, partial, aborted} ⇒ anchorrequired (conditionalallOf)step.downstreamis present ⇒step.span_idrequired (conditionalallOf)docs/schema/v1.1.json— loose bridge schema accepting the current v1.x event shape. Additive only — used by theruntime's bridge path in §5.3, not by the v2 SDK.
Golden fixtures
testdata/fixtures/v2/— six golden parity fixtures, one per status, covering every reserved lifecycle code:ok-simple.jsonerror-payment-cascade.json(canonical PMT_502 cascade — primary demo scenario)timeout-watchdog.json(WAYLOG_TIMEOUT)aborted-cancel.json(WAYLOG_ABORTED)suppressed-healthcheck.jsonerror-panic.json(WAYLOG_PANIC)Go package —
pkg/event/v2New package
eventv2. Coexists with the existing v1.1pkg/eventduring the bridge window:Event,Anchor,Step,Downstream,StepError,Log,ErrorRefSchemaVersion2,StatusOK/Error/Timeout/Partial/Aborted/Suppressed,CodeTimeout/Aborted/Panic/PartialValidate(schemaPath, *Event)andValidateFile(schemaPath, eventPath)backed by the v2.0 JSON Schema, sharing onevalidateAnyhelper.SpanIDandParentSpanIDintentionally lackomitemptyso SDK-emitted events always carry both as string fields —parent_span_id: ""is a real, observable signal for root-of-trace, not a missing key.Tests
TestFixturesValidateAgainstSchema— every fixture validates againstdocs/schema/v2.0.json. Sub-tests run inparallel.
TestFixtureRoundTripStructural— the parity gate. For each fixture: unmarshal intoEvent, marshal back, maskruntime-generated identity fields with non-empty sentinels (so an explicit
parent_span_id: ""cannot be silentlydropped via
omitempty+ empty-string normalization), normalize semantic-empty values, structurally compare. CatchesJSON-tag drift, accidental
omitemptyon must-have-fields, and unilateral struct additions. Sub-tests run in parallel.TestV11BridgeSchemaAcceptsCurrentV1Event— builds a realevent.WideEventfrompkg/event/, validates againstdocs/schema/v1.1.json. Future drift in either side fails this test.TestV11BridgeSchemaRejectsMissingTraceID— sanity check that the bridge schema is not vacuous.TestV20SchemaRequiresStatus— negative test that locks instatusas required.TestEventRoundTrip,TestStatusConstants— basic shape sanity.Module
pkg/go.mod— addsgithub.com/santhosh-tekuri/jsonschema/v5 v5.3.1. No other deps. (google/uuiddeferred — notused yet.)
Code-review fixes folded in
Four findings raised across two review rounds, all closed before merge:
TestFixtureRoundTripStructuralwith masking +normalization, designed to be safe for both pinned fixtures and live SDK output.
TestV11BridgeSchemaAcceptsCurrentV1Eventand the negative rejecttest.
statusto top-levelrequiredand locked it in with anegative test.
parent_span_idcould silently drop → tightened the parity mask to use a non-empty sentinel for explicitempty-string identity values, then removed
omitemptyfromSpanIDandParentSpanID. TDD'd: confirmed the tightenedtest fails on current code (6/6 sub-tests red), then removed
omitemptyand watched it go green.A subsequent simplification pass also tightened the surface:
ValidateandValidateFilenow share avalidateAnyhelper instead of duplicating the compile-then-validate flow; the one-shot
encodeForValidationhelper is inlined; the"schema"resource handle is a named constant; verbose narration comments were trimmed; tests use theSchemaVersion2const instead of string literals.Deviations from the plan-on-disk
Three small judgment calls, made deliberately:
eventv2instead ofevent. Plan Task 4 originally said "replacepkg/event"; we agreedoffline to coexist with v1.1 instead so the repo keeps building. Naming the new package
eventv2avoids any same-nameshadowing.
bytesReader/jsonUnmarshalaroundbytes.NewReaderandjson.Unmarshal). No behavior change.jvCLI gate replaced withTestFixturesValidateAgainstSchema.github.com/santhosh-tekuri/jsonschemadoesn't ship a CLI binary. Validation is now a permanent CI test, which is stronger than a one-off shell command.
Test plan
jq -e . docs/schema/v2.0.jsonandjq -e . docs/schema/v1.1.jsonparse cleanlycd pkg && go test ./event/v2/... -v→ 17/17 PASS, fixture sub-tests run in parallelgo vet ./pkg/event/v2/...cleango test ./...at repo root → all packages green; v1.1 path untouchedgo build ./...at repo root → cleanOut of scope (next slice)
pkg/waylogpublic surface (Config, Init, Step, Fail, Suppress, Explain, Stats)pkg/waylog/httpmiddleware with lifecycle precedence + anchor synthesispkg/transport/httpNDJSON delivery worker (two-class queue, retry, gzip)bench/perf-budget gate