Skip to content

feat(schema): v2.0 schema foundation + Go validator (Phase 1 slice 1)#12

Merged
sssmaran merged 1 commit into
masterfrom
v2-phase-1
Apr 25, 2026
Merged

feat(schema): v2.0 schema foundation + Go validator (Phase 1 slice 1)#12
sssmaran merged 1 commit into
masterfrom
v2-phase-1

Conversation

@sssmaran

Copy link
Copy Markdown
Owner

Summary

First slice of the v2 plan in docs/v2-plan.md — the schema foundation that the Go SDK rewrite, TS SDK, and runtime
ingest 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, delivery
worker, 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:
    • Top-level required: schema_version, event_id, ts_start, ts_end, kind, service, env, trace_id,
      status
    • status ∈ {error, timeout, partial, aborted} ⇒ anchor required (conditional allOf)
    • step.downstream is present ⇒ step.span_id required (conditional allOf)
  • docs/schema/v1.1.json — loose bridge schema accepting the current v1.x event shape. Additive only — used by the
    runtime'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.json
    • error-payment-cascade.json (canonical PMT_502 cascade — primary demo scenario)
    • timeout-watchdog.json (WAYLOG_TIMEOUT)
    • aborted-cancel.json (WAYLOG_ABORTED)
    • suppressed-healthcheck.json
    • error-panic.json (WAYLOG_PANIC)

Go package — pkg/event/v2

New package eventv2. Coexists with the existing v1.1 pkg/event during the bridge window:

  • Types: Event, Anchor, Step, Downstream, StepError, Log, ErrorRef
  • Constants: SchemaVersion2, StatusOK/Error/Timeout/Partial/Aborted/Suppressed, CodeTimeout/Aborted/Panic/Partial
  • Validate(schemaPath, *Event) and ValidateFile(schemaPath, eventPath) backed by the v2.0 JSON Schema, sharing one
    validateAny helper.
  • SpanID and ParentSpanID intentionally lack omitempty so 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 against docs/schema/v2.0.json. Sub-tests run in
    parallel.
  • TestFixtureRoundTripStructural — the parity gate. For each fixture: unmarshal into Event, marshal back, mask
    runtime-generated identity fields with non-empty sentinels (so an explicit parent_span_id: "" cannot be silently
    dropped via omitempty + empty-string normalization), normalize semantic-empty values, structurally compare. Catches
    JSON-tag drift, accidental omitempty on must-have-fields, and unilateral struct additions. Sub-tests run in parallel.
  • TestV11BridgeSchemaAcceptsCurrentV1Event — builds a real event.WideEvent from pkg/event/, validates against
    docs/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 in status as required.
  • TestEventRoundTrip, TestStatusConstants — basic shape sanity.

Module

  • pkg/go.mod — adds github.com/santhosh-tekuri/jsonschema/v5 v5.3.1. No other deps. (google/uuid deferred — not
    used yet.)

Code-review fixes folded in

Four findings raised across two review rounds, all closed before merge:

  1. [High] No structural round-trip parity test → added TestFixtureRoundTripStructural with masking +
    normalization, designed to be safe for both pinned fixtures and live SDK output.
  2. [Medium] v1.1 bridge schema unproven → added TestV11BridgeSchemaAcceptsCurrentV1Event and the negative reject
    test.
  3. [Medium] v2.0 schema didn't require status → added status to top-level required and locked it in with a
    negative test.
  4. [P2] parent_span_id could silently drop → tightened the parity mask to use a non-empty sentinel for explicit
    empty-string identity values, then removed omitempty from SpanID and ParentSpanID. TDD'd: confirmed the tightened
    test fails on current code (6/6 sub-tests red), then removed omitempty and watched it go green.

A subsequent simplification pass also tightened the surface: Validate and ValidateFile now share a validateAny
helper instead of duplicating the compile-then-validate flow; the one-shot encodeForValidation helper is inlined; the
"schema" resource handle is a named constant; verbose narration comments were trimmed; tests use the
SchemaVersion2 const instead of string literals.

Deviations from the plan-on-disk

Three small judgment calls, made deliberately:

  1. Sub-package name eventv2 instead of event. Plan Task 4 originally said "replace pkg/event"; we agreed
    offline to coexist with v1.1 instead so the repo keeps building. Naming the new package eventv2 avoids any same-name
    shadowing.
  2. Inlined two pass-through wrappers the plan defined (bytesReader/jsonUnmarshal around bytes.NewReader and
    json.Unmarshal). No behavior change.
  3. Plan's jv CLI gate replaced with TestFixturesValidateAgainstSchema. github.com/santhosh-tekuri/jsonschema
    doesn'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.json and jq -e . docs/schema/v1.1.json parse cleanly
  • cd pkg && go test ./event/v2/... -v → 17/17 PASS, fixture sub-tests run in parallel
  • go vet ./pkg/event/v2/... clean
  • go test ./... at repo root → all packages green; v1.1 path untouched
  • go build ./... at repo root → clean

Out of scope (next slice)

  • pkg/waylog public surface (Config, Init, Step, Fail, Suppress, Explain, Stats)
  • pkg/waylog/http middleware with lifecycle precedence + anchor synthesis
  • pkg/transport/http NDJSON delivery worker (two-class queue, retry, gzip)
  • Redaction, dev dual-emit, watchdog
  • bench/ perf-budget gate
  • TS SDK (Phase 1c)
  • Runtime ingest server, read APIs, dashboard, CLI rewrite (Phase 2)

  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.
@sssmaran sssmaran merged commit 3cbbad2 into master Apr 25, 2026
1 check passed
@sssmaran sssmaran deleted the v2-phase-1 branch April 25, 2026 18:21
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