Spec ID: gs1-1.0.0
Date: 2025-06-20
Status: Frozen (v1.0)
This document specifies the GS1 stream framing protocol for GLYPH payloads. GS1 headers are NOT part of GLYPH canonicalization.
GS1 is a stream framing protocol for transporting a sequence of GLYPH payloads over streaming transports (TCP, WebSocket, pipes, files, SSE).
- GS1 does not modify GLYPH syntax or canonicalization.
- A GS1 frame contains:
- A GS1 header (stream metadata)
- A payload that is valid GLYPH text (UTF-8 bytes)
- Canonicalization, schema validation, and patch semantics are properties of the payload, not GS1.
- A GS1 implementation MUST NOT require changes to the GLYPH parser.
- The GS1 reader is a separate layer that outputs
payloadBytesto the normal GLYPH decoder.
| Term | Definition |
|---|---|
| Frame | One message in the stream (header + payload) |
| SID | Stream identifier (multiplex key) |
| SEQ | Per-SID sequence number (monotonic) |
| KIND | Semantic category of the payload |
| BASE | Optional state hash for patch safety |
| CRC | Optional CRC-32 checksum for integrity |
| Value | Name | Meaning |
|---|---|---|
| 0 | doc |
Snapshot or general GLYPH document/value |
| 1 | patch |
GLYPH patch doc (@patch ... @end) |
| 2 | row |
Single row value (streaming tabular data) |
| 3 | ui |
UI event value (progress/log/artifact refs) |
| 4 | ack |
Acknowledgement (usually no payload) |
| 5 | err |
Error event (payload describes error) |
| 6 | ping |
Keepalive / liveness check |
| 7 | pong |
Ping response |
Implementations MUST accept unknown kinds and surface them as unknown(<byte>).
GS1-T is the text-based wire format, suitable for SSE, WebSocket text frames, logs, and debugging.
@frame{key=value key=value ...}\n
<exactly len bytes of payload>
\n
The header line starts with @frame{ and ends with }\n.
Inside {} is a space-separated or comma-separated list of key=value pairs.
Required keys:
| Key | Type | Description |
|---|---|---|
v |
uint8 | Protocol version (MUST be 1) |
sid |
uint64 | Stream identifier |
seq |
uint64 | Sequence number (per-SID, monotonic) |
kind |
string/uint8 | Frame kind (name or number) |
len |
uint32 | Payload length in bytes |
Optional keys:
| Key | Type | Description |
|---|---|---|
crc |
string | CRC-32 of payload: crc32:<8hex> or <8hex> |
base |
string | State hash: sha256:<64hex> |
final |
bool | End-of-stream marker for this SID |
flags |
uint8 | Bitmask (hex) |
Receiver MUST read payload as raw bytes using len.
Receiver MUST NOT parse payload boundaries using delimiters.
- Writer MUST emit a trailing
\nafter payload. - Reader SHOULD consume trailing
\nbut MUST accept EOF.
@frame{v=1 sid=1 seq=12 kind=patch len=32 crc=89abcdef base=sha256:0123456789abcdef...}
@patch
set .foo 42
@end
GS1-B is reserved for future implementation. Binary header layout:
magic 3 bytes "GS1"
ver 1 byte uint8 (1)
flags 1 byte uint8
kind 1 byte uint8
sid 8 bytes uint64 big-endian
seq 8 bytes uint64 big-endian
len 4 bytes uint32 big-endian
[crc] 4 bytes uint32 (if HAS_CRC)
[base] 32 bytes (if HAS_BASE)
payload len bytes
Flags bits:
- bit 0 (
0x01) =HAS_CRC - bit 1 (
0x02) =HAS_BASE - bit 2 (
0x04) =FINAL - bit 3 (
0x08) =COMPRESSED(reserved for GS1.1)
When crc is present:
- Algorithm: CRC-32 IEEE (polynomial 0xEDB88320)
- Input: payload bytes as transmitted
- Format in GS1-T:
crc=<8 lowercase hex digits>orcrc=crc32:<8hex>
Receiver MUST verify CRC if present and reject frame on mismatch.
When base is present:
- Algorithm: SHA-256
- Input:
CanonicalizeStrict(stateDoc)orCanonicalizeLoose(stateDoc) - Format in GS1-T:
base=sha256:<64 lowercase hex digits>
base = sha256( Canonicalize(stateDoc) )
Sender and receiver MUST agree on canonicalization mode (Strict vs Loose).
This is the stream state hash. In Go, the source-of-truth helper is
stream.StateHashLoose, which hashes glyph.CanonicalizeLoose(stateDoc).
Do not substitute a high-level value fingerprint helper unless both sender
and receiver use the same canonical byte definition.
For kind=patch frames with base:
- Receiver MUST NOT apply patch if
receiverStateHash != base - On mismatch, receiver SHOULD:
- Request a
docsnapshot, OR - Emit an
errframe, OR - Emit an
ackwith failure payload
- Request a
For each sid:
seqMUST be monotonically increasing by 1- Receivers SHOULD detect gaps and handle appropriately
kind=ackacknowledges receipt of(sid, seq)ackframes typically havelen=0ackwith payload may carry error/status details
final=trueindicates no more frames for thissid- Receiver may clean up per-SID state
UIEvent@(type "progress" pct 0.42 msg "processing")
UIEvent@(type "log" level "info" msg "decoded 1000 rows")
UIEvent@(type "artifact" mime "image/png" ref "blob:sha256:..." name "plot.png")Err@(code "BASE_MISMATCH" sid 1 seq 13 expected "sha256:..." got "sha256:...")Payload is a single GLYPH value (struct/list) representing one row.
@patch
set .items[0].qty 5
append .items[+] Item@(id 2 name "widget")
@end- CRC-32 is not cryptographic; it only detects accidental corruption.
basehash prevents accidental state drift but is not authentication.- Implementations MUST enforce maximum
len(recommended: 64 MiB). - Use TLS for transport security; GS1 does not provide encryption.
A GS1 implementation is conformant if it:
- Correctly reads/writes GS1-T frames per this spec
- Enforces
lenlimits - Verifies CRC-32 when present
- Parses
basehash correctly - Exposes
(sid, seq, kind, payloadBytes, base?, crc?)to caller - Does not require GLYPH parser changes
- Does not treat GS1 headers as part of GLYPH canonicalization
Header: @frame{v=1 sid=0 seq=0 kind=doc len=2}
Payload: {}
Full frame:
@frame{v=1 sid=0 seq=0 kind=doc len=2}
{}
Header: @frame{v=1 sid=1 seq=5 kind=patch len=24 crc=a1b2c3d4}
Payload: @patch\nset .x 1\n@end
Header: @frame{v=1 sid=1 seq=10 kind=ui len=35}
Payload: UIEvent@(type "progress" pct 0.5)
Header: @frame{v=1 sid=1 seq=10 kind=ack len=0}
Payload: (empty)
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2025-06-20 | Initial frozen spec (GS1-T only) |
For GS1-T, kind can be specified as name or number:
kind=doc <==> kind=0
kind=patch <==> kind=1
kind=row <==> kind=2
kind=ui <==> kind=3
kind=ack <==> kind=4
kind=err <==> kind=5
kind=ping <==> kind=6
kind=pong <==> kind=7
Unknown numeric kinds (8+) are valid and preserved.