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
2 changes: 2 additions & 0 deletions .github/workflows/tests-golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ on:
- scripts/check-wasm-imports.py
- scripts/go-binding-test.sh
- scripts/stack-conformance.sh
- scripts/stack-conformance-selection.mjs
- Cargo.toml
- Cargo.lock
- .cargo/**
Expand Down Expand Up @@ -97,6 +98,7 @@ on:
- scripts/check-wasm-imports.py
- scripts/go-binding-test.sh
- scripts/stack-conformance.sh
- scripts/stack-conformance-selection.mjs
- Cargo.toml
- Cargo.lock
- .cargo/**
Expand Down
20 changes: 19 additions & 1 deletion conformance/stack-encrypt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,31 @@ native Rust against the ciphertexts Go has just produced. It never regenerates
expectations. CI also runs the Go cases on its other supported host platforms.
The same task runs the shared declaration acceptance/rejection corpus in both
languages, with native observations of normalization. It also runs the shared
runtime and read cases described below.
runtime and read cases, and enrolls the existing value-kind and query-operator
fixtures described below. Set no fixture-update variables when checking conformance.

The Rust runner is `packages/stack-encrypt/tests/conformance.rs`; the Go runner
is `languages/golang/encrypt/conformance_test.go`. Go calls the record interface
used by generated code. Testing public SDK declarations is a separate layer.
Python and TypeScript runners are pending; their absence is not a passing result.

## Existing value-kind and query-operator fixtures

The [fixture contract](fixture-contract.md) defines `record-value-kinds-v1` and
`record-query-operators-v1`. Their [enrollment index](v1/fixture-profiles.json)
maps stable requirement IDs to the existing Rust and Go tests. The fixtures
remain under `packages/stack-encrypt/tests/fixtures`; no vectors are copied.

These profiles cover the represented narrow/128-bit integers, dates,
timestamps, decimals, equality/order terms, query operand metadata, match
verification and fetch limits. They preserve typed Rust and generated Go API
checks. The task runs the existing tests directly, and a missing guest fails
when conformance is required. The ordinary CI suites already execute them.

This is representative fixture coverage, not an exhaustive type/operator
matrix. Wider fresh artifact exchange, shared query refusals and EQL/JSON
operations remain separate work.

## `record-reads-v1` contract

The [read contract](read-contract.md) defines cases in `v1/reads.json` for
Expand Down
18 changes: 13 additions & 5 deletions conformance/stack-encrypt/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Paths are relative to the repository root. New profile cases run from `v1/`.

| Requirement | Existing evidence | Remaining work |
|---|---|---|
| Scalar value widths and lossless transport | `packages/stack-encrypt/tests/value_kinds.rs`; `languages/golang/encrypt/value_kinds_test.go`; initial shared scalar cases | Expand shared cases to decimals, dates, timestamps, floats, bytes and all widths; Python/TS mappings |
| Scalar value widths and lossless transport | Initial scalar cases and [value-kind requirements](fixture-contract.md#value-kind-requirements), executed by the existing Rust and generated Go runners | Complete per-kind boundary matrices, bytes and broader floats; Python/TS mappings |
| Owned opaque ciphertext and storage envelope | `packages/stack-encrypt/tests/scalar.rs`, `frozen_bytes.rs`; initial cross-opening cases | Blob profile; encoded targets; supported version matrix |
| Record declaration and lowering | [Declaration grammar](declaration-grammar.md); `v1/declarations.json` shared by native Rust and Go; native normalization observations; existing record lowering suites | EQL-resolver profile; SDK-to-declaration mappings; runtime source/read shapes |
| Context binding and composition | `packages/stack-encrypt/tests/target.rs`, `descriptor.rs`, `record_lowering.rs`; `languages/golang/encrypt/context_field_test.go`; initial wrong-context cases | Shared composition vectors; expected-context and required-input matrix |
| Query terms and diagnostics | `packages/stack-encrypt/tests/query_operators.rs`; `languages/golang/encrypt/query_test.go`; initial equality/query-error cases | Common rejection corpus for every operator, capability and input shape |
| Query terms and diagnostics | [Query-operator requirements](fixture-contract.md#query-operator-requirements), existing Rust/Go runners and initial equality/query-error cases | Common rejection corpus for every operator, capability and input shape; wider kind/index combinations |
| Missing, SQL NULL and sealed null | `packages/stack-encrypt/src/dynamic/record.rs` null tests; `languages/golang/encrypt/null_test.go` | Explicit language mappings and common missing/null fixtures |
| Fresh cross-language storage | `.github/workflows/tests-golang.yml` exports fresh Go EQL rows for Rust; initial scalar artifact exchange | Every producer/consumer/backend pair and supported released versions |
| JSON queries, extraction and tampering | `packages/stack-encrypt/tests/json_tree_vectors.rs`; `packages/eql/tests/encryption/tests/json_tree.rs`; `languages/golang/encrypt/json_tree_test.go`, `json_postgres_test.go` | Formal operation profile; JSON vectors consumed by all runners |
Expand Down Expand Up @@ -59,17 +59,25 @@ raw-byte profile must cover that boundary explicitly.

## Consolidation policy

The value-kind/query-operator enrollment adds named requirements for the
existing fixtures through [the enrollment index](v1/fixture-profiles.json).
The conformance command selects their original runners; CI guards the mapping
and selection. Typed Rust comparisons, generated Go methods, verification and
fetch-limit checks stay in those runners. No fixture is copied and no existing
assertion is removed. Shared negative cases and fresh exchange of the wider
value kinds remain open.

Shared semantic expectations should be maintained once and executed by each
language runner. Conformance can replace duplicate case tables and fixture
maintenance. It does not eliminate the need to execute the Rust and Go paths.
No existing test has been deleted in these first four slices.
No existing test has been deleted by conformance enrollment.

| Existing evidence | Relationship to conformance | Consolidation action |
|---|---|---|
| Rust `dynamic/record.rs` tests `a_malformed_plan`, `a_null_is_refused_as_its_key`, `a_missing_or_null_index_payload_is_the_default` | Many inputs and diagnostic outcomes are now shared declaration cases | Move the remaining cases and retain any unique display/low-level assertions before removing duplicate rows |
| Rust kind/index admission tests | The corpus has the complete 21-kind/five-index acceptance matrix | Share case data; retain direct tests that compare declaration admission with actual term execution |
| Rust `value_kinds.rs`, Go `value_kinds_test.go` | Already share `value_kinds.json`; cover more types and generated SDK code than the initial scalar profile | Enroll that existing fixture under named requirements instead of copying it; keep generated-code assertions |
| Rust `query_operators.rs`, Go `query_test.go` | Already share operator vectors, including verification and target limits | Reuse their fixture and cases when adding an operator profile; preserve host/generated query mapping tests |
| Rust `value_kinds.rs`, Go `value_kinds_test.go` | Enrolled as `record-value-kinds-v1`, retaining the shared fixture and generated SDK calls | Extend the existing fixture/requirements for missing boundaries; retain typed and generated-code assertions |
| Rust `query_operators.rs`, Go `query_test.go` | Enrolled as `record-query-operators-v1`, including verification and target limits | Add shared refusal cases separately; retain host/generated query mapping tests |
| Go checker and host validation tests in `guest_test.go` | Exercise host rejection and diagnostic forwarding through public checker methods | Keep adapter-specific assertions; representable declaration cases also use `Checker.Check`, while raw frames observe the guest boundary |
| Native scalar/record round trips and cross-opening | Some behavior overlaps, but existing tests cover more types and API paths | Replace only exact duplicates after matching requirements and execution paths |

Expand Down
110 changes: 110 additions & 0 deletions conformance/stack-encrypt/fixture-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Value-kind and query-operator profiles

`record-value-kinds-v1` and `record-query-operators-v1` enroll the existing
Rust/Go fixtures in the conformance contract. They are experimental profiles
of the cases those fixtures contain, not exhaustive type/operator certification.
They use the plain record resolver (`NoTargets`), with public deterministic
keys and no database or live credentials.

The fixtures remain at their existing paths:

- [`value_kinds.json`](../../packages/stack-encrypt/tests/fixtures/value_kinds.json)
- [`query_operators.json`](../../packages/stack-encrypt/tests/fixtures/query_operators.json)

Their [existing schema documentation](../../packages/stack-encrypt/tests/fixtures/README.md)
continues to own the formats. No expected frames, ciphertexts, terms, verified
pages or fetch limits are copied into this directory.

[`v1/fixture-profiles.json`](v1/fixture-profiles.json) maps stable requirement IDs
to the existing Rust test functions and Go tests that provide their evidence.
It is an enrollment index, not another input corpus or a runtime capability
negotiation format. The CI wiring test checks fixture paths, source declarations,
requirement documentation and exact command selection. Rust also checks each
binary’s compiled test inventory and rejects missing or ignored enrolled tests.
The Go missing-guest regression checks each indexed test’s actual failure/skip
outcome, so a test excluded from compilation cannot silently pass enrollment.
Both language implementations are mandatory for these profiles; Python and TypeScript remain pending.

## Value-kind requirements

The fixture represents int8, int16, uint8, uint16, int128, uint128, date,
timestamp and decimal values. It includes the signed 128-bit minimum and
unsigned 128-bit maximum. These are representative cases, not every boundary
of every type. Values use the fixture's `type` and `text` with a frozen `frame`;
128-bit integers must never pass through a floating-point JSON number.

| Requirement ID | Required observation | Fixture evidence |
|---|---|---|
| `values.typed-frames` | The actual host value encodes to the exact frozen typed frame, preserving integer width, decimal scale and timestamp nanoseconds. | Every `values` entry |
| `values.frozen-open` | The committed Rust record opens to those values and types, retaining decimal scale and timestamp precision. | `record` and `values` |
| `values.stored-terms` | Fresh records round-trip and store the frozen equality/order terms. Queries for their values derive the corresponding stored terms. Randomized ciphertext bytes are not compared. | Every non-`c` output in `record`, under `plan` |
| `values.normalized-queries` | Decimal queries differing only in trailing zeros and timestamp queries truncated to microseconds derive the stored equality and order terms, while the opened values retain their original precision. | Every `queries` entry and its field's order output |

Rust additionally compares dynamic execution with typed term derivation and
retains its wrong-width write refusal test. Go runs the real generated
`testusers.ValueKinds` encrypt/decrypt and field-query methods, including its
field-context substitution refusal. These assertions stay in their owning
tests. The separate Go invalid-date/decimal/timestamp tests remain in the full
Go suite; they are not claimed as shared fixture requirements here.

## Query-operator requirements

The fixture covers equality on text and uint32, scalar/range ordering on uint32
and float64, and probabilistic text matching. Its operators are `eq`, `lt`,
`lte`, `gt`, `gte`, `between` and `matches`, in the combinations declared by
the fixture. It does not claim every operator for every kind or index.

| Requirement ID | Required observation | Fixture evidence |
|---|---|---|
| `queries.term-contract` | Derive the frozen operand bytes and metadata: operator, fidelity, index, domain, and scalar versus ordered low/high range shape. | Every `queries` entry |
| `queries.match-verification` | Verify the complete page in input order, including matches, nonmatches and null; results agree with the frozen boolean vector. | `verify` |
| `queries.fetch-limits` | Given the frozen match term, requested count and statistics, return the recorded bounded row count or explicit unbounded result. | Every `target_limit` entry |

Rust compares the dynamic path with typed query chains, `MatchQuery::verify`
and typed fetch sizing, and checks that the fixture contains both verification
outcomes and both bounded/unbounded limits. Go exercises the record interface
and generated query methods. Generated string-page verification omits null,
which the record-interface test covers. Its existing SQL parameter conversion,
empty-page, zero-query, inverted-range and floating-point ordering assertions
remain in those selected tests. Wider operator refusal and secret-diagnostic
tests remain in the full suites; this slice does not turn them into shared
negative cases. In Go, `TestAQueryTheFieldDoesNotAnswerIsRefused` and
`TestAMatchesQueryNeverPrintsItsText` share the fixture client, so they also
fail on a missing guest in required mode. They remain outside the aggregate
selection and the shared requirements; the missing-guest regression covers them.

## Running and maintaining the profiles

`mise run test:conformance` selects the existing `value_kinds` and
`query_operators` Rust binaries and the Go tests listed in the enrollment index,
alongside the earlier profiles. The Go selection is generated from the index;
adding an enrolled Go test does not require editing a shell regular expression.
Fixture assertions stay in their owning tests; the separate missing-guest probes
stop at client creation. The ordinary Rust and Go
CI suites already run these tests, so enrollment adds no duplicate CI suite.

When `STACK_CONFORMANCE_REQUIRED=1`, a missing deterministic guest fails the
enrolled Go tests instead of skipping them. The aggregate command rejects both
`STACK_CONFORMANCE_UPDATE` and `STACK_ENCRYPT_UPDATE_FIXTURES`, even when set to
an empty value. All four Rust fixture writers, including the two record-lowering
writers,
independently reject updates in required mode. A subprocess regression exercises
the guard with absent, empty and nonempty update flags without writing fixtures.
Deliberate fixture maintenance remains available outside
required mode through the existing documented regeneration commands.

Keep requirement IDs stable. Add a new profile version for incompatible
expectations, and review fixture regeneration explicitly. The enrollment
index's `version` is its metadata format, not a production storage version.

This slice consolidates the coverage inventory and execution selection, not
the underlying test assertions. No test or frozen artifact is removed. Fresh
cross-language artifact exchange remains the scalar profile's guarantee;
these wider fixtures prove frozen Rust-to-Go opening, local round trips and
term agreement, not fresh Go-to-Rust opening of every represented kind.

Python and TypeScript should consume these same fixture paths and implement
the listed observations through their own host values and public APIs. Their
integer/decimal/time mappings and actual backends must be specified by those
implementations. EQL domains, JSON operations, all-type boundary matrices,
shared query refusals and supported-release matrices remain separate work.
83 changes: 83 additions & 0 deletions conformance/stack-encrypt/v1/fixture-profiles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"version": 1,
"profiles": [
{
"id": "record-value-kinds-v1",
"fixture": "packages/stack-encrypt/tests/fixtures/value_kinds.json",
"rust_binary": "value_kinds",
"go_source": "languages/golang/encrypt/value_kinds_test.go",
"requirements": [
{
"id": "values.typed-frames",
"rust_tests": [
"the_committed_fixture_opens_and_its_terms_are_todays"
],
"go_tests": ["TestGeneratedCodeOpensTheRustValueKindsFixture"]
},
{
"id": "values.frozen-open",
"rust_tests": [
"the_committed_fixture_opens_and_its_terms_are_todays"
],
"go_tests": ["TestGeneratedCodeOpensTheRustValueKindsFixture"]
},
{
"id": "values.stored-terms",
"rust_tests": [
"every_new_kind_seals_opens_and_queries_as_the_typed_path",
"the_committed_fixture_opens_and_its_terms_are_todays"
],
"go_tests": ["TestGeneratedCodeOpensTheRustValueKindsFixture"]
},
{
"id": "values.normalized-queries",
"rust_tests": [
"every_new_kind_seals_opens_and_queries_as_the_typed_path",
"the_committed_fixture_opens_and_its_terms_are_todays"
],
"go_tests": ["TestGeneratedCodeOpensTheRustValueKindsFixture"]
}
]
},
{
"id": "record-query-operators-v1",
"fixture": "packages/stack-encrypt/tests/fixtures/query_operators.json",
"rust_binary": "query_operators",
"go_source": "languages/golang/encrypt/query_test.go",
"requirements": [
{
"id": "queries.term-contract",
"rust_tests": [
"the_committed_query_fixture_is_what_the_engine_derives"
],
"go_tests": [
"TestQueryTermsAreTheRustFixtures",
"TestGeneratedQueriesAreTheRustFixtures"
]
},
{
"id": "queries.match-verification",
"rust_tests": [
"the_committed_query_fixture_is_what_the_engine_derives",
"the_fixture_pins_both_outcomes"
],
"go_tests": [
"TestVerifyIsTheRustFixtures",
"TestAMatchesQueryVerifiesAndSizesAsTheRustFixtures"
]
},
{
"id": "queries.fetch-limits",
"rust_tests": [
"the_committed_query_fixture_is_what_the_engine_derives",
"the_fixture_pins_both_outcomes"
],
"go_tests": [
"TestTargetLimitIsTheRustFixtures",
"TestAMatchesQueryVerifiesAndSizesAsTheRustFixtures"
]
}
]
}
]
}
Loading
Loading