Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test-eql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,9 @@ jobs:
mise run test:crates

# Freshness gate for the eql-types codegen output: regenerate the
# TypeScript bindings and fail if the checked-in copies differ.
# Reuses the toolchain from the step above.
- name: Verify eql-types bindings are fresh
# TypeScript bindings and JSON Schemas and fail if the checked-in
# copies differ. Reuses the toolchain from the step above.
- name: Verify eql-types bindings and schemas are fresh
run: |
mise run types:check

Expand Down
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions crates/eql-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
name = "eql-types"
version = "0.1.0"
edition = "2021"
description = "Canonical wire types for EQL payloads — the single Rust source of truth, with generated TypeScript bindings (JSON Schemas are generated from these types in a stacked change)."
description = "Canonical wire types for EQL payloads — single source of truth for Rust, TypeScript (ts-rs), and JSON Schema (schemars)."

[dependencies]
serde = { version = "1", features = ["derive"] }
# Direct dependency again at this layer: SchemaVersion's manual JsonSchema
# impl pins `const: 2` via serde_json::json!.
serde_json = "1"
ts-rs = "10"
schemars = "0.8"

[dev-dependencies]
# Parity oracle: tests/catalog_parity.rs asserts the v3 domain inventory
# exactly covers eql_scalars::CATALOG, so the types here cannot drift from
# the generated SQL surface.
eql-scalars = { path = "../eql-scalars" }
serde_json = "1"
25 changes: 14 additions & 11 deletions crates/eql-types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ shape**, the single source of truth for every tool that produces or consumes
EQL payloads (`cipherstash-client`, `protect-ffi`, CipherStash Proxy).

TypeScript bindings are generated from these definitions via [`ts-rs`] into
[`bindings/`](bindings/); JSON Schemas (via [`schemars`]) follow in a
stacked change.
[`bindings/`](bindings/), and JSON Schemas via [`schemars`] into
[`schema/`](schema/).

## Why

Expand Down Expand Up @@ -56,21 +56,24 @@ the catalog by the JSON Schema parity test in the stacked schemars change.
## Develop

```sh
mise run types:generate # clean-regenerate bindings/
mise run types:check # regenerate + fail if checked-in bindings are stale
mise run types:generate # clean-regenerate bindings/ and schema/
mise run types:check # regenerate + fail if checked-in outputs are stale
```

Both wrap `cargo test -p eql-types`, which runs the conformance tests and
regenerates `bindings/` (TypeScript, via ts-rs). The directory is checked in
so reviewers can see the codegen output without running anything; CI runs
`types:check` to keep it fresh. The crate is also part of the lean
regenerates `bindings/` (TypeScript, via ts-rs) and `schema/` (JSON Schema,
via `tests/export.rs`, with canonical `$id`s injected). Both directories are
checked in so reviewers can see the codegen output without running anything;
CI runs `types:check` to keep them fresh. The crate is also part of the lean
`mise run test:crates` set (fmt, clippy, test — no database).

Note that ts-rs writes to `./bindings` by default, so a plain
Note that both exporters default to writing under the crate dir (ts-rs to
`./bindings`, `tests/export.rs` to `./schema`), so a plain
`cargo test -p eql-types` (and therefore `mise run test:crates`) regenerates
`bindings/` **in place** as a side effect — it can leave your working tree
dirty if the checked-in copies were stale. Only `types:generate` isolates the
write (it exports into a temp dir and swaps it in after the build succeeds).
`bindings/` and `schema/` **in place** as a side effect — it can leave your
working tree dirty if the checked-in copies were stale. Only `types:generate`
isolates the writes (it exports into a temp dir and swaps them in after the
build succeeds).

## Future direction: self-describing payloads

Expand Down
2 changes: 1 addition & 1 deletion crates/eql-types/bindings/v3/BloomFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* Bloom-filter match term — the `bf` wire key. Backs the `_match` domains
* (`~~` containment via `@>`/`<@`).
* (`@>`/`<@` containment).
*
* **Signed** i16, not u16: EQL stores the filter as PostgreSQL `smallint[]`,
* and filters sized above 32768 emit upper-half bit positions as negative
Expand Down
69 changes: 69 additions & 0 deletions crates/eql-types/schema/v3/date.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"$id": "https://schemas.cipherstash.com/eql/v3/date.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"Ciphertext": {
"description": "mp_base85 source ciphertext — the `c` envelope key.\n\nRequired by every v3 domain CHECK; present on every payload.",
"type": "string"
},
"Identifier": {
"additionalProperties": false,
"description": "Table + column identifier — wire shape `{\"t\": \"...\", \"c\": \"...\"}`.\n\nShared by every payload.",
"properties": {
"c": {
"description": "Column name.",
"type": "string"
},
"t": {
"description": "Table name.",
"type": "string"
}
},
"required": [
"c",
"t"
],
"type": "object"
},
"SchemaVersion": {
"const": 2,
"description": "The envelope version field (`v`) — always exactly `2` on the wire.",
"type": "integer"
}
},
"description": "`eql_v3.date` — storage only; every operator is blocked.",
"properties": {
"c": {
"allOf": [
{
"$ref": "#/definitions/Ciphertext"
}
],
"description": "mp_base85 source ciphertext. Required by the domain CHECK."
},
"i": {
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
],
"description": "Table/column identifier. Required by the domain CHECK."
},
"v": {
"allOf": [
{
"$ref": "#/definitions/SchemaVersion"
}
],
"description": "Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other value fails deserialization."
}
},
"required": [
"c",
"i",
"v"
],
"title": "Date",
"type": "object"
}
82 changes: 82 additions & 0 deletions crates/eql-types/schema/v3/date_eq.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"$id": "https://schemas.cipherstash.com/eql/v3/date_eq.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"Ciphertext": {
"description": "mp_base85 source ciphertext — the `c` envelope key.\n\nRequired by every v3 domain CHECK; present on every payload.",
"type": "string"
},
"Hmac256": {
"description": "HMAC-SHA-256 equality term — the `hm` wire key. Backs the `_eq` domains (`=`, `<>`). SQL-side constructor: `eql_v3.hmac_256`.",
"type": "string"
},
"Identifier": {
"additionalProperties": false,
"description": "Table + column identifier — wire shape `{\"t\": \"...\", \"c\": \"...\"}`.\n\nShared by every payload.",
"properties": {
"c": {
"description": "Column name.",
"type": "string"
},
"t": {
"description": "Table name.",
"type": "string"
}
},
"required": [
"c",
"t"
],
"type": "object"
},
"SchemaVersion": {
"const": 2,
"description": "The envelope version field (`v`) — always exactly `2` on the wire.",
"type": "integer"
}
},
"description": "`eql_v3.date_eq` — HMAC equality (`=`, `<>`).",
"properties": {
"c": {
"allOf": [
{
"$ref": "#/definitions/Ciphertext"
}
],
"description": "mp_base85 source ciphertext. Required by the domain CHECK."
},
"hm": {
"allOf": [
{
"$ref": "#/definitions/Hmac256"
}
],
"description": "HMAC-SHA-256 equality term."
},
"i": {
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
],
"description": "Table/column identifier. Required by the domain CHECK."
},
"v": {
"allOf": [
{
"$ref": "#/definitions/SchemaVersion"
}
],
"description": "Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other value fails deserialization."
}
},
"required": [
"c",
"hm",
"i",
"v"
],
"title": "DateEq",
"type": "object"
}
85 changes: 85 additions & 0 deletions crates/eql-types/schema/v3/date_ord.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"$id": "https://schemas.cipherstash.com/eql/v3/date_ord.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"Ciphertext": {
"description": "mp_base85 source ciphertext — the `c` envelope key.\n\nRequired by every v3 domain CHECK; present on every payload.",
"type": "string"
},
"Identifier": {
"additionalProperties": false,
"description": "Table + column identifier — wire shape `{\"t\": \"...\", \"c\": \"...\"}`.\n\nShared by every payload.",
"properties": {
"c": {
"description": "Column name.",
"type": "string"
},
"t": {
"description": "Table name.",
"type": "string"
}
},
"required": [
"c",
"t"
],
"type": "object"
},
"OreBlockU64_8_256": {
"description": "Block-ORE (u64, 8 blocks, 256) order term — the `ob` wire key. Backs the `_ord` / `_ord_ore` domains (`=` `<>` `<` `<=` `>` `>=`); ORE is lossless over the scalar's domain, so it serves equality too. SQL-side constructor: `eql_v3.ore_block_u64_8_256`.",
"items": {
"type": "string"
},
"type": "array"
},
"SchemaVersion": {
"const": 2,
"description": "The envelope version field (`v`) — always exactly `2` on the wire.",
"type": "integer"
}
},
"description": "`eql_v3.date_ord` — full comparison (`=` `<>` `<` `<=` `>` `>=`).",
"properties": {
"c": {
"allOf": [
{
"$ref": "#/definitions/Ciphertext"
}
],
"description": "mp_base85 source ciphertext. Required by the domain CHECK."
},
"i": {
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
],
"description": "Table/column identifier. Required by the domain CHECK."
},
"ob": {
"allOf": [
{
"$ref": "#/definitions/OreBlockU64_8_256"
}
],
"description": "Block-ORE order term. Serves equality too."
},
"v": {
"allOf": [
{
"$ref": "#/definitions/SchemaVersion"
}
],
"description": "Envelope version — always `2` (`EQL_SCHEMA_VERSION`); any other value fails deserialization."
}
},
"required": [
"c",
"i",
"ob",
"v"
],
"title": "DateOrd",
"type": "object"
}
Loading
Loading