Skip to content

P1: JSON-bridge int64 fidelity + schema-text round-trip (Stage 5) - #8

Merged
phenomenon0 merged 1 commit into
mainfrom
feat/p1-json-and-schematext
Jun 20, 2026
Merged

P1: JSON-bridge int64 fidelity + schema-text round-trip (Stage 5)#8
phenomenon0 merged 1 commit into
mainfrom
feat/p1-json-and-schematext

Conversation

@phenomenon0

Copy link
Copy Markdown
Contributor

Stage 5 — P1 JSON-bridge fidelity + schema-text round-trip

Branches off main. Go-only. Closes the remaining P1 gaps from the parity roadmap (the schema-hash ship-blocker is handled separately in #5 feat/p1-schema-hash).

JSON bridge (json_bridge.go)

  • int64 precision (the cited json_bridge.go:213 bug): ToJSONLoose of an Int GValue now emits a full integer literal via json.Number instead of float64(v.intVal), so values above 2^53 no longer silently round on emit.
  • Extended-marker collision-safety: the $glyph key is reserved in extended mode. Emitting a map/struct key or sum tag named $glyph is now a loud error rather than a silently ambiguous marker; on decode, only exactly-shaped marker objects are accepted (extra/missing companion keys are rejected).
  • Documented the maintainer decision that the Loose layer intentionally collapses to JSON-like semantics (time/id/bytes → string, struct → object with TypeName dropped, sum → {tag:value}). Typed round-trip is the canonical Parse/Emit path's job only.

Deliberate deviation from the roadmap (surfaced, not blended)

The roadmap asked FromJSONLoose to use json.Decoder.UseNumber so large ints "survive" on input. That conflicts with:

  1. the maintainer "Loose = JSON-like collapse" decision, and
  2. the cross-impl parity gate — goldens 016_large_int_like / 035_safe_int_boundary require 2^53+1 to render as the float 9.007199254740992e+15, matching JS number (which cannot hold such integers).

So input keeps JSON-like float64 collapse; only the emit direction preserves int64. This is documented in the code and is the correct reconciliation per "surface conflicts, pick the more recent/tested."

Schema text (token.go, parse.go, schema.go)

  • Lexer now tokenizes the .. range operator so [0..10] lexes (a lone . is still a lex error). The Range constraint String() no longer double-brackets, so it round-trips as [min..max].
  • EmitSchema/ParseSchema now round-trip the packed-decoding-relevant subset: @pack/@tab/@open type flags and per-field @fid(N), @k(wire), @codec(name), @keepnull, and scalar @default(...). (Previously @open was emitted but the parser rejected it.)
  • ParseSchema(EmitSchema(s)) is now a fixpoint for that subset — the test asserts text + hash stability and per-field survival.

Remainder (explicitly de-scoped, noted per roadmap)

Container/bytes @default values and the min-len/max-len/regex/enum/unique constraints emit but are not yet parsed back from schema text; they still round-trip through the programmatic schema API.

Verification

ulimit -v 6000000; go test -C go ./glyph/ -skip 'Industrial|Cliff|Perf|Savings|Benchmark' -count=1 → green, incl. TestCrossImpl_* parity. go vet + go build ./... clean.

New tests: json_bridge_fidelity_test.go, schema_text_roundtrip_test.go.

🤖 Generated with Claude Code

JSON bridge (json_bridge.go):
- ToJSONLoose now emits an Int GValue as a full integer literal (json.Number)
  instead of float64(v.intVal), so values above 2^53 no longer silently lose
  precision on the emit side (the bug cited at json_bridge.go:213).
- Extended-mode "$glyph" marker namespace is now collision-safe: emitting a
  map/struct key or sum tag named "$glyph" is a loud error (not a silently
  ambiguous marker), and on decode only exactly-shaped marker objects are
  accepted (extra/missing companion keys are rejected).
- Documented the maintainer decision that the Loose layer INTENTIONALLY
  collapses to JSON-like semantics (time/id/bytes -> string, struct -> object
  with TypeName dropped, sum -> {tag:value}); typed round-trip is the canonical
  Parse/Emit path's job only.

Deliberate deviation from the roadmap (surfaced, not averaged): the roadmap
asked FromJSONLoose to use json.Decoder.UseNumber so large ints "survive" on
input. That is incompatible with the maintainer "JSON-like collapse" decision
AND the cross-impl parity gate (goldens 016/035 require 2^53+1 to render as the
float 9.007199254740992e+15, matching JS `number`, which cannot hold such
integers). So input keeps JSON-like float64 collapse; only the emit direction
preserves int64. This is documented in the code.

Schema text (token.go, parse.go, schema.go):
- Lexer tokenizes the ".." range operator so [0..10] lexes (a lone "." stays a
  lex error). Range constraint String() no longer double-brackets, so it
  round-trips as [min..max].
- EmitSchema/ParseSchema now round-trip the packed-decoding-relevant subset:
  @pack/@tab/@OPEN type flags, and per-field @fid(N), @k(wire), @codec(name),
  @keepnull, and scalar @default(...). Previously @OPEN was emitted but the
  parser rejected it.
- ParseSchema(EmitSchema(s)) is now a fixpoint for that subset (test asserts
  text + hash stability and per-field survival).

Remainder (noted): container/bytes @default values and the
min-len/max-len/regex/enum/unique constraints emit but are not yet parsed back
from schema TEXT; they still round-trip through the programmatic schema API.

Tests: json_bridge_fidelity_test.go, schema_text_roundtrip_test.go. Full Go
suite green (go test ./glyph -skip 'Industrial|Cliff|Perf|Savings|Benchmark'),
including cross-impl parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@phenomenon0
phenomenon0 force-pushed the feat/p1-json-and-schematext branch from 1c1231a to b88e8a8 Compare June 20, 2026 01:51
@phenomenon0
phenomenon0 merged commit 38e9dbf into main Jun 20, 2026
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