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
12 changes: 11 additions & 1 deletion conformance/stack-encrypt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@ It runs native Rust against frozen vectors, Go against the same vectors, then
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.
languages, with native observations of normalization. It also runs the shared
runtime cases described below.

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.

## `record-runtime-v1` contract

The [runtime contract](runtime-contract.md) defines cases in `v1/runtime.json`:
missing and explicit null values, SQL-null omission, sealed-null storage and
equality, typed batch round trips, write validation and lossless query conversions.
The runners are `packages/stack-encrypt/tests/runtime_conformance.rs` and
`languages/golang/encrypt/runtime_conformance_test.go`. Native Rust also checks
data-key call counts; Go records where its host adapter refuses inputs earlier.

## `record-declarations-v1` contract

The [declaration grammar](declaration-grammar.md) defines the engine's current
Expand Down
18 changes: 17 additions & 1 deletion conformance/stack-encrypt/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,28 @@ Paths are relative to the repository root. New profile cases run from `v1/`.
Python and future TypeScript runners have no evidence in this checkout. Their
absence is a delivery gap, not a test skip that counts toward certification.

## Runtime checkpoint

`v1/runtime.json` adds shared cases for typed writes and reads, null storage,
batches, equality queries and lossless numeric conversion. See the
[runtime contract](runtime-contract.md) for exact observations and limits.
Native data-key counters cover zero-call preflight failures and single-call
batches; Go provider-call instrumentation remains open.

| Existing evidence | Shared case IDs | Retained assertions |
|---|---|---|
| Native SQL-null record tests | `sql-missing`, `sql-null`, `sql-mixed-batch`, `sql-all-absent` | Wider declarations and malformed stored-record behavior |
| Native sealed-null padding/equality tests | `sealed-mixed-batch`, `sealed-int64`, `sealed-uint64`, `sealed-float64` | Other integer widths, bool ciphertext, dates, timestamps and decimals |
| Go generated `null_test.go` | Same sealed/SQL semantics | Pointer mapping, generated methods, EQL envelopes, reading null into nonpointer fields |
| Native source validation tests | `late-wrong-type`, `late-missing`, `unknown-field` | Repeated keys, unsupported frame shapes and more output kinds |
| Existing shared value-kind/operator fixtures | Runtime query cases cover selected conversion edges | Full type/operator matrices, generated SDK calls and verification |

## Consolidation policy

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 two slices.
No existing test has been deleted in these first three slices.

| Existing evidence | Relationship to conformance | Consolidation action |
|---|---|---|
Expand Down
89 changes: 89 additions & 0 deletions conformance/stack-encrypt/runtime-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Record runtime profile

`record-runtime-v1` describes the behavior exercised by `v1/runtime.json`.
It is an experimental test contract for native Rust and Go WASI, with no EQL
resolver. Python and TypeScript runners remain pending.

Each case uses one field, `value`, with ciphertext (`c`) and equality (`eq`)
outputs, under context `["conformance", "value"]`. `kind` declares its type;
`null` is empty for a required value, `sql`, or `sealed`. Values use the scalar
profile's `{type, text}` representation, extended with `float64` decimal text.
Runners construct typed values before calling the engine. This profile admits
`int32`, `int64`, `uint64`, `float64`, and `string` as declared kinds;
both runners reject unknown kinds and null-policy spellings.

## Write and read

The `seal` operation receives an ordered `rows` array of field maps. An absent
map key is missing; `{ "type": "null", "text": "" }` is an explicit null.
These are separate inputs, including in languages that offer a nil-like value.

Successful cases specify `expected_rows` and `expected_stored`. The former
describes opened values with their types and order. The latter states whether
each row stores the field at all. Both must have the input row count.

- Required fields reject missing values and explicit nulls.
- SQL-null fields accept both, omit the stored field, and open as explicit null.
- Sealed-null fields require the key to exist. An explicit null stores both
ciphertext and equality, then opens as null.
- Runtime writes require the declared numeric type. A losslessly convertible
value of another width is still refused on this path.
- Unknown fields are refused, including when an omitted SQL-null field makes
the input field count match the declaration; they cannot be silently dropped.
- An invalid later row rejects the whole batch before data-key generation.
- An empty batch succeeds with no data-key calls.

Every stored equality term must match a query for its expected opened value.
`equal_lengths` additionally requires all stored ciphertexts in the case to
have equal lengths. Mixed sealed-null/value cases exercise this requirement
for int32, int64, uint64 and float64. This is not a claim about all types.
Boolean equality is not admitted; existing native null tests cover boolean
ciphertext separately.

## Equality queries

The `query` operation receives `input` and either `expected_value` or
`expected_error`. A successful query's term must equal the term stored by
encrypting `expected_value` through the same declaration. That value is
independent expected data, not a conversion computed by the runner.

Cases cover signed narrowing bounds, integral and fractional float inputs,
negative-to-unsigned refusal, and exact versus rounded integer-to-float
conversion around 2^53. Text and boolean values are not parsed as numbers.
A null equality query succeeds only for sealed-null fields.

## Refusals and observations

`expected_error` specifies the engine diagnostic `code`, `reason`, and `field`.
Native Rust must reject before producing a pending operation. Go must expose
`ErrEncoding` and the same diagnostic when the request reaches the guest.

Missing and unknown fields can be rejected earlier by Go's record adapter.
The adapter preserves the same structured source diagnostic (code, reason and
field); the corpus does not require a particular English message or rejection
at the host boundary. Forwarding to the guest later remains conformant.

Each refused source row contains exactly one independent fault. The corpus does
not specify precedence between multiple faults or source-object traversal order.
Batch cases may place that single invalid row after valid rows to test preflight.

The native runner instruments data-key generation and retrieval calls. A
successful batch makes one of each when it stores ciphertext, and zero when
every field is omitted. Refused writes and all queries make neither call.
The query oracle's separate encryption is counted separately. These assertions
exclude index-key initialization and do not establish Go provider-call counts.
Go runs the same semantic cases through its deterministic WASI guest; that
guest's lack of network requests is not proof of logical key-call counts.

## Scope and remaining work

The cases cover runtime writes, corresponding reads, and equality queries.
They do not cover malformed stored records, authenticated type mismatches on
read, context composition/preflight, provider failures, or other operators.
Fresh cross-language artifact exchange remains the scalar profile's coverage.

Existing `value_kinds.json` and `query_operators.json` fixtures remain the
shared evidence for their wider type/operator matrices. Existing generated
Go null tests retain pointer mapping and EQL assertions. Native null tests
retain their wider kinds and lower-level padding assertions. No tests are
deleted by this slice; consolidation requires matching those assertions first.
Loading
Loading