Skip to content

Commit 1ab800d

Browse files
authored
test(encrypt): add read conformance
1 parent 42d9b56 commit 1ab800d

11 files changed

Lines changed: 1816 additions & 9 deletions

File tree

‎conformance/stack-encrypt/README.md‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@ native Rust against the ciphertexts Go has just produced. It never regenerates
2020
expectations. CI also runs the Go cases on its other supported host platforms.
2121
The same task runs the shared declaration acceptance/rejection corpus in both
2222
languages, with native observations of normalization. It also runs the shared
23-
runtime cases described below.
23+
runtime and read cases described below.
2424

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

30+
## `record-reads-v1` contract
31+
32+
The [read contract](read-contract.md) defines 26 cases in `v1/reads.json` for
33+
malformed stored records, authenticated read-type failures and context preflight.
34+
The native runner observes retrieval stages. Go exercises the same cases through
35+
both an uninitialised guest and an active guest, plus the normal record adapter
36+
for representable cases. The runners are `tests/read_conformance.rs` in the Rust
37+
crate and `encrypt/read_conformance_test.go` in the Go module.
38+
3039
## `record-runtime-v1` contract
3140

3241
The [runtime contract](runtime-contract.md) defines 37 cases in `v1/runtime.json`:

‎conformance/stack-encrypt/coverage.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,30 @@ batches; Go provider-call instrumentation remains open.
3939
| Native source validation tests | `late-wrong-type`, `late-missing`, `unknown-field` | Repeated keys, unsupported frame shapes and more output kinds |
4040
| Existing shared value-kind/operator fixtures | Runtime query cases cover selected conversion edges | Full type/operator matrices, generated SDK calls and verification |
4141

42+
## Read checkpoint
43+
44+
`v1/reads.json` adds 26 cases for stored-shape validation, authenticated types
45+
and context preflight. The [read contract](read-contract.md) distinguishes native
46+
shape/pending stages and the guest's earlier `check_record` diagnostics. Native
47+
data-key counters and Go's uninitialised guest verify preflight boundaries.
48+
49+
| Existing evidence | Shared case IDs | Retained assertions |
50+
|---|---|---|
51+
| Native `a_stored_record_that_does_not_fit` | `missing-field`, `missing-ciphertext`, `field-not-map`, `plaintext-under-c`, `row-not-map`, `late-missing-field` | Repeated keys, other passthrough fields and output kinds |
52+
| Native typed read/null tests | `authenticated-int32-as-*`, `authenticated-null-as-required`, `authenticated-null-as-sql`, `sql-absent-*` | Wider types and declaration combinations |
53+
| Go `context_field_test.go`, native context tests | `context-unrestricted`, `context-expected`, `context-mismatch`, `context-late-mismatch`, `context-drop-tenant`, `context-tenant-*` | Generated APIs, queries, extension composition and stored-context tampering |
54+
| Native and guest context-source validation | `static-context-with-expectation` | Other source combinations and empty-batch behavior |
55+
56+
These are overlap mappings, not grounds for deleting whole mixed-purpose tests.
57+
Repeated-key trees cannot be emitted by Go's map-based tree encoder; a future
58+
raw-byte profile must cover that boundary explicitly.
59+
4260
## Consolidation policy
4361

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

4967
| Existing evidence | Relationship to conformance | Consolidation action |
5068
|---|---|---|
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Record read profile
2+
3+
`record-reads-v1` adds 26 shared cases in `v1/reads.json` for stored record
4+
shapes, authenticated type checks and context preflight. It fixes the resolver
5+
to `NoTargets`. It is experimental and covers native Rust and Go WASI.
6+
7+
## Inputs and observations
8+
9+
Each case has separate `write` and `read` declarations, each naming the `kind`
10+
and `null` policy of `value`. Outputs are ciphertext and equality. `rows` uses
11+
the runtime profile's typed scalar representation. Runners first encrypt these
12+
rows successfully, then apply the named `mutation` to its zero-based `row`.
13+
This supplies real ciphertext, including for negative reads.
14+
15+
Without `context_field`, the context is `["conformance", "value"]`. With it,
16+
`tenant` is a string passthrough supplying the row context; `value` is its
17+
field identity. An optional `expected_context` constrains the stored context.
18+
Its value must be a nonempty plain label.
19+
20+
`expected` describes the native opener's stage, complete diagnostic code and
21+
payload on refusal, typed ordered rows on success, and data-key retrieval
22+
call count. Runners must reject unknown stages, mutations, capabilities,
23+
profiles and input types. Cases have unique, nonempty IDs.
24+
25+
| Stage | Native observation | Retrieval calls |
26+
|---|---|---|
27+
| `shape` | `record::decrypt` refuses before returning a pending operation | 0 |
28+
| `preflight` | The pending opener refuses before requesting keys | 0 |
29+
| `authenticated` | Shape checking passes; opening the authenticated value finds a type mismatch | 1 |
30+
| `success` | Opened rows match all expected values, types and ordering | 1 when ciphertext is present; 0 for the all-absent SQL-null case |
31+
32+
Counters are reset after fixture encryption. Reads must never generate data
33+
keys. These counts observe native data-key calls, not index-key initialization
34+
or Go's logical provider calls.
35+
36+
`check_error` independently describes `record::check_record`, which needs no
37+
cipher. An absent `check_error` requires acceptance. The Go guest runs this
38+
check before looking up its cipher state. Both an uninitialised guest and an
39+
active guest execute each case: preflight errors must have the specified
40+
diagnostic in either; accepted shapes must reach `ErrState` without a cipher.
41+
Consequently, a concealed type mismatch must pass preflight and fail only when
42+
the active guest opens it.
43+
44+
The Go runner uses a test-only entry point to the existing `se_decrypt_record`
45+
export. This permits shapes the host's `record.Sealed` type cannot express,
46+
including a passthrough directly under `c`. It uses the binding's existing
47+
tree codec, options and transport-buffer cleanup. Cases marked `adapter` also
48+
run the normal `Cipher.Open` path; they retain their original stored shape.
49+
This does not certify generated SDK mappings.
50+
51+
## Required behavior
52+
53+
- Missing required fields, missing ciphertext outputs, non-map rows or field
54+
outputs, and plaintext passthroughs under ciphertext are refused before keys.
55+
- A malformed later row rejects the batch before retrieval.
56+
- Reads project the fields and outputs the plan opens. Extra stored fields,
57+
extra outputs and missing equality outputs do not prevent reading ciphertext.
58+
Writes still reject unknown source fields, as the runtime profile requires.
59+
- Reading int32 ciphertext as int64, uint64 or string fails after retrieval.
60+
Reads do not apply the query path's lossless numeric conversion.
61+
- A sealed null cannot open through a required declaration. It can open through
62+
a SQL-null declaration, with retrieval because ciphertext is present.
63+
- An absent SQL-null field opens as null without retrieval. A required or
64+
sealed-null read declaration refuses the same missing field.
65+
- Context-field reads work without an expected label or with a matching label.
66+
A mismatch, including one in a later row, refuses the whole batch before keys.
67+
- Missing context fields, missing passthrough payloads and non-string context
68+
values fail before retrieval.
69+
- A static-context plan cannot also take a call context. Native `check_record`
70+
and the guest report `dynamic_record` with reason `refused`; the native pending
71+
opener reports `plan_two_context_sources`, naming the plan and the call.
72+
The corpus records both existing diagnostics explicitly.
73+
74+
Context-mismatch diagnostic payloads expose only stored descriptor length and
75+
part count, not its value. Go diagnostic numeric metadata is compared as JSON
76+
numbers; plaintext comparisons retain actual host types.
77+
78+
## Mutation vocabulary
79+
80+
| Mutation | Change to the selected row |
81+
|---|---|
82+
| `none` | Preserve it |
83+
| `drop_value` / `drop_c` / `drop_eq` | Remove the named field or output |
84+
| `field_plain` / `c_plain` / `row_plain` | Replace that node with an int32 passthrough |
85+
| `extra_field` / `extra_output` | Add an `extra` int32 passthrough at that level |
86+
| `drop_tenant` | Remove the context field |
87+
| `tenant_no_payload` | Replace its output map with an empty map |
88+
| `tenant_number` | Replace its passthrough string with an int32 |
89+
90+
## Scope and consolidation
91+
92+
The existing native record tests retain repeated keys, invalid leaf framing,
93+
other tree forms, context extensions and wider type/output combinations. Go's
94+
tree encoder accepts maps with unique keys, so repeated-key transport cases
95+
need a separate raw-byte profile rather than being silently skipped here.
96+
97+
The existing Go context-field suite retains generated SDK calls, query context
98+
selection, extension behavior and stored-context tampering. The deterministic
99+
provider's wrong-context refusal remains covered there and by the scalar
100+
profile. This profile observes preflight context checks, not every context
101+
composition or cryptographic tampering case.
102+
103+
No existing tests are removed. Python and TypeScript runners, EQL/JSON read
104+
shapes, provider failures, version compatibility and runtime artifact exchange
105+
remain follow-up work.

‎conformance/stack-encrypt/runtime-contract.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ guest's lack of network requests is not proof of logical key-call counts.
7474
The 37 cases cover runtime writes, corresponding reads, and equality queries.
7575
They do not cover malformed stored records, authenticated type mismatches on
7676
read, context composition/preflight, provider failures, or other operators.
77+
The separate [read profile](read-contract.md) adds stored-shape, authenticated
78+
type and context-preflight cases.
7779
Fresh cross-language artifact exchange remains the scalar profile's coverage.
7880

7981
Existing `value_kinds.json` and `query_operators.json` fixtures remain the

0 commit comments

Comments
 (0)