You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b9f84a5
Browse filesBrowse the repository at this point in the historyBrowse files
feat(stack-encrypt): let a target sealed under its own context opt in to queries on its plan
A typed plan answers an operator through its target type's `op::Answers`
impl, but the chain required `Typed<T>: ValueShape<S>`: a label that
converts into `T`'s context. An EQL v4 type is sealed under its column
(`NonEmpty<Identifier>`), so no plan for one could be queried, and
eql-bindings had nowhere to implement `Answers` (#1238).
`op::LabelledTarget` is the opt-in: a target type says its stored terms
were derived under the context a plan's label names. For such a type,
`Chooses` asks only `ValueLayout<S>` (the indexes `T` declares), since a
query derives through the index and never runs `T`'s encryption. Every
other type keeps needing `ValueShape`, so a type sealed under, say, a
tenant id cannot be queried under a label that would match nothing.
This crate's term targets implement the trait, which keeps every query
that compiled before compiling. `Chooses` now resolves the plan itself
(`choose`) rather than through `Resolves`, which keeps `ValueShape`: a
named index (`.via(..)`) on a plan whose type only opts in would send a
term the type does not send.
An EQL query value stores the column it queries, so its index needs the
context its terms were derived under, and must refuse one it cannot
record. `QueryToken`, which only the chain makes, now carries the label
and whether the call extended it (`label`, `is_extended`): with
`.extend(..)` the terms are derived under the label and the extension, so
a term that records the label alone would match nothing. The defaulted
`Supports::check_label` refuses such a context, and the chain runs it in
`check`, where the label is resolved, before any keyset is loaded or term
derived (and again in `prepare`, as for `check`). `Supports::query` stays
infallible: the chain calls it only after `check_label` accepted the
token's context. Every index here takes any context.
Tests: a labelled target is queried under the plan's or the call's label,
and its term reads whether the call extended it; a label its index
refuses loads no keyset; and a UI case pins that a type with an `Answers`
impl but no `LabelledTarget` answers no query on its plan.
Copy file name to clipboardExpand all lines: packages/stack-encrypt/docs/adr/0009-operators-select-the-index-and-a-query-term-is-not-the-stored-term.md
/// A target type whose stored terms were derived under the context a
855
+
/// query's label names, so a plan for it
856
+
/// (`Plan::value::<S>().encrypt_into::<T>()`) answers the operators its
857
+
/// [`Answers`] impls name, with each query derived under the plan's label.
858
+
///
859
+
/// Implementing it is a claim about the stored values: a query term derived
860
+
/// under the label equals the stored term only if the stored term was
861
+
/// derived under the same context. This crate's term targets
862
+
/// ([`EqualityTerm`], [`MatchTerms`], [`OreTerm`], [`OpeTerm`], and
863
+
/// [`Encrypted`](crate::target::Encrypted) or a ciphertext beside one
864
+
/// term) are sealed under the caller's context, which is the label. An EQL
865
+
/// v4 type is sealed under the column it stores, which a two-segment label
866
+
/// names, and its index refuses any other label
867
+
/// ([`Supports::check_label`]). A type sealed under a context no label
868
+
/// names, such as an integer tenant id, must not implement it: a query on
869
+
/// its plan would match nothing, with no error.
870
+
///
871
+
/// A query on such a plan derives through the index alone and never runs
872
+
/// the target's own encryption, so the target need not accept the label as
873
+
/// its context: an EQL type, sealed under a column identifier, cannot be
874
+
/// encrypted through its plan and is queried through it all the same.
875
+
#[diagnostic::on_unimplemented(
876
+
message = "`{Self}` is not queried under a plan's label",
877
+
label = "a typed plan for this target type answers no query",
878
+
note = "a target type answers queries on its plan when it implements `op::LabelledTarget`: its stored terms were derived under the context the plan's label names"
/// A field of a fields plan and a one-value plan built `with` indexes
713
728
/// answer through the operator's [`DefaultIndex`]; a one-value plan for a
714
-
/// target type answers through the type's [`Answers`] impl. The plan's
729
+
/// target type answers through the type's [`Answers`] impl, when the type
730
+
/// is a [`LabelledTarget`], whose stored terms were derived under the
731
+
/// context the plan's label names. Such a plan is queried through the
732
+
/// indexes the type declares ([`ValueLayout`]) even when the type cannot be
733
+
/// encrypted through it, as an EQL type sealed under its column cannot.
734
+
/// The plan's
715
735
/// plaintext is the input's ([`Input::Plaintext`]): `S` for a query given
716
736
/// an `S`, and for a `Between` given an `(S, S)`. `At` is the position of
717
737
/// that index in a plan built `with` several, and is inferred.
@@ -720,15 +740,21 @@ where
720
740
label = "no index answers this operator on this plan",
721
741
note = "a field of a fields plan or a plan built `with` indexes answers an operator through its default index (`op::Eq`: `Equality`; `op::Matches`: `Match` with the default configuration, case-insensitive and over text only; `op::Lt`, `Lte`, `Gt`, `Gte` and `Between`: `Ore` over an integer, `Between` given a pair); a typed plan answers the operators its target type implements `Answers` for; `.via(index)` names an index the plan declares"
0 commit comments