Skip to content

[3/6] feat: EQL v3 type checker driven by per-column domain capability#424

Open
freshtonic wants to merge 10 commits into
queue/eql-v3/upgrade-depsfrom
queue/eql-v3/typecheck
Open

[3/6] feat: EQL v3 type checker driven by per-column domain capability#424
freshtonic wants to merge 10 commits into
queue/eql-v3/upgrade-depsfrom
queue/eql-v3/typecheck

Conversation

@freshtonic

@freshtonic freshtonic commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

📚 eql-v3 PR  ·  3 of 6

Part of a queue. The PRs merge in FIFO order — the numbered order below, #1 first. Merging one supersedes the PRs after it until the author runs git queue sync (rebases the rest onto the merged base) and git queue submit (retargets their PRs).

✅🟢 #422 queue/eql-v3/setup-skillsmain
✅🟢 #423 queue/eql-v3/upgrade-depsqueue/eql-v3/setup-skills
♻️🟢 #424 queue/eql-v3/typecheckqueue/eql-v3/upgrade-deps  👈 this PR
✅🟢 #428 queue/eql-v3/transformqueue/eql-v3/typecheck
✅🟢 #426 queue/eql-v3/showcasequeue/eql-v3/transform
⏳🟢 #427 queue/eql-v3/integrationqueue/eql-v3/showcase

✅ approved · ♻️ changes requested · ⏳ review pending  |  🟣 merged · 🟢 open · ⚫ closed  —  status as of the last git queue submit.
🥞 Managed by git-queue — do not edit this list by hand.

About this queue

Migrates CipherStash Proxy from EQL v2 to EQL v3 (cipherstash-client 0.34.1-alpha.4 → 0.42.0, EQL 2.3.0-pre.3 → 3.0.2), replacing the opaque eql_v2_encrypted composite type with 53 typed jsonb domains that encode both scalar type and searchable capability in the column type itself.

About this branch

Type checker for v3 domains — extends EQL Mapper to emit eql_v3_<token>_<capability> domains instead of eql_v2_encrypted
(designed, not yet built; the step that makes the migration actually work).

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d09c1c2-f763-4488-82f8-9fcd4d6f8a51

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch queue/eql-v3/typecheck

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@freshtonic
freshtonic force-pushed the queue/eql-v3/upgrade-deps branch from 479744c to 86a8731 Compare July 21, 2026 13:06
@freshtonic
freshtonic force-pushed the queue/eql-v3/typecheck branch 3 times, most recently from 5e9bd60 to 64b7811 Compare July 22, 2026 05:51
@freshtonic freshtonic changed the title [3/3] docs: handoff for the EQL v3 type checker design [3/4] docs: handoff for the EQL v3 type checker design Jul 22, 2026
@freshtonic freshtonic changed the title [3/4] docs: handoff for the EQL v3 type checker design [3/5] docs: handoff for the EQL v3 type checker design Jul 22, 2026
@freshtonic
freshtonic requested a review from tobyhede July 22, 2026 21:42
@freshtonic
freshtonic force-pushed the queue/eql-v3/typecheck branch from c251fb8 to 3cad2c5 Compare July 22, 2026 21:46
@freshtonic freshtonic changed the title [3/5] docs: handoff for the EQL v3 type checker design [3/4] docs: handoff for the EQL v3 type checker design Jul 22, 2026
@freshtonic freshtonic changed the title [3/4] docs: handoff for the EQL v3 type checker design [3/5] docs: handoff for the EQL v3 type checker design Jul 22, 2026
@freshtonic freshtonic changed the title [3/5] docs: handoff for the EQL v3 type checker design [3/4] docs: handoff for the EQL v3 type checker design Jul 22, 2026
@freshtonic freshtonic changed the title [3/4] docs: handoff for the EQL v3 type checker design [3/6] docs: handoff for the EQL v3 type checker design Jul 22, 2026
@tobyhede

Copy link
Copy Markdown
Contributor

Verified the v3 domain mapping against the actual eql-bindings 3.0.1 catalog: all nine term-key combinations it emits ([], ["hm"], ["op"], ["ob"], ["bf"], and the unions) map correctly, and storage-only eql_v3_json (Some(&[]) → none) is correctly distinguished from searchable eql_v3_json_search/_entry (None → JsonLike+Contain). The two bug fixes are correct and the regression tests encode the old buggy output as the discriminator — nice.

Findings, most substantive first.

1. eql_v3_json_entry is classified with inverted semantics. public.eql_v3_json_entry is in v3::all() and ste_vec_domain_type! leaves term_json_keys at the default None, so resolve("eql_v3_json_entry") returns JsonLike + Contain. But the installed SQL comments it as 'EQL encrypted JSONB leaf entry (equality, ordering)' — it's the -> result element, and its per-entry terms are hm XOR op. So a column of that type would type-check as supporting ->/@> and not =/<, which is backwards. No user declares such a column today, so it's latent, but every_public_column_domain_resolves_to_a_known_token_type actively asserts it resolves. Consider excluding the entry/document operand types from the column catalog rather than relying on nobody declaring them.

2. DomainIdentity::canonical collides with real domains that mean something else. canonical(Text, {json_like, ..}) takes the if traits.json_like { push("search") } branch and produces eql_v3_text_search — which is a real catalog domain whose terms are [hm, op, bf] (Eq+Ord+TokenMatch), not a JSON capability. This is live: notes (EQL: JsonLike + Contain) appears at eight sites in lib.rs tests plus type_env.rs:259. It can also emit genuinely non-catalog names (Eq+TokenMatcheql_v3_text_eq_match, Containeql_v3_text_contain). The doc comment concedes it "is not guaranteed to equal the exact catalog typname" — worth strengthening that to "may collide with an unrelated real domain", or deriving the suffix so it can't produce a real-but-wrong name.

3. Test-fixture footgun from the new identity field. EqlValue derives PartialEq, so identity now participates in equality. Production is safe — both construction sites read traits and identity from the same ColumnKind::Eql on the same Column, so identity is a function of TableColumn and adds no discrimination (and EqlTerm::Partial's union applies to the bounds field, not EqlValue.2, so nothing desyncs). But fixtures can now disagree: a column declared with the explicit arm EQL("eql_v3_integer_ord"): Ord carries identity eql_v3_integer_ord, while an expected projection![(EQL(t.c: Ord))] builds the canonical eql_v3_text_ordassert_eq! fails on a dimension the test wasn't testing. Loud rather than silent, but a papercut for whoever writes the transform tests next.

4. docs: is the wrong commit type. ~346 added lines are production Rust/SQL, including a new production dependency, a breaking public API change (ColumnKind::Eql gains a field, Column::eql gains an arg, ColumnKind loses Copy), a schema-loader rewrite, and a real behaviour fix (self.eq ^ other.eqself.eq && !other.eq). The PR's own commits say feat/fix/refactor for three of seven. The body's "designed, not yet built" also undersells it. Matters for changelog generation and bisect.

5. v2 columns → native with only a warn! is right for a v3-only build, and it's not a plaintext-read exposure (a native passthrough returns the raw composite ciphertext). One caveat: eql_v2.to_encrypted does no validation and CREATE CAST (text AS eql_v2_encrypted) is AS ASSIGNMENT, so on the write path an explicitly-text-typed, JSON-parseable value ('42'::text) could be wrapped and stored in plaintext rather than erroring. Narrow, but it means "fails loudly" isn't quite airtight — worth making that warning hard to miss in ops.

6. No CHANGELOG entry — CLAUDE.md asks for one on user-facing changes. Not specific to this PR: none of #422/#423/#424/#426/#427/#428 touch CHANGELOG.md, while the queue collectively changes a public signature, swaps the v2 composite for v3 domains, and bumps cipherstash-client 0.34→0.42. Raising against the queue, not this branch.

@tobyhede tobyhede left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appears to be a working document.
Should this be committed?

Copy link
Copy Markdown
Contributor Author

@tobyhede I believe it should be - it's valuable context

@freshtonic
freshtonic force-pushed the queue/eql-v3/upgrade-deps branch from 86a8731 to 21495ac Compare July 23, 2026 06:08
freshtonic added a commit that referenced this pull request Jul 23, 2026
…h a real domain

Review of the v3 domain-identity work (#424) noted that canonical() can
synthesise a name that is not merely approximate but identical to an
unrelated real catalog domain — canonical(Text, {json_like}) yields
eql_v3_text_search, whose terms are hm/op/bf (Eq+Ord+TokenMatch), nothing
to do with JSON. Strengthen the doc comment so no caller mistakes the
synthesised name for the column's real domain; only from_domain_name is
authoritative.

Stable-Commit-Id: q-1ewbmpx74jnyxfd7dyftv08ka9
@freshtonic
freshtonic force-pushed the queue/eql-v3/typecheck branch from 3cad2c5 to 1a81cf2 Compare July 23, 2026 06:08
freshtonic added a commit that referenced this pull request Jul 23, 2026
The queue changes user-visible behaviour (v3 typed jsonb domains replace
eql_v2_encrypted, cipherstash-client 0.42.0 / EQL 3.0.2, LIKE/ILIKE now
capability-checked, `@@` newly supported) but touched no CHANGELOG entry.
Add one [Unreleased] section covering the migration, per CLAUDE.md and the
review notes on #424/#428.

Stable-Commit-Id: q-0s2fx8fb1q2563t7b45x9crjmf
@freshtonic
freshtonic requested a review from tobyhede July 23, 2026 07:36
freshtonic added a commit that referenced this pull request Jul 23, 2026
Review of #424 (finding 5) noted the v2->native fallback should be hard to
miss in ops, because a v2 column served as a native passthrough takes no
encryption on the write path: a plaintext value written to it is stored in
plaintext (the eql_v2_encrypted AS ASSIGNMENT cast does no validation).

Spell that out in the warning — the column is served as PLAINTEXT, Proxy
does no encrypt/decrypt on it, migrate before writing — instead of the
softer "ignoring unsupported column".

Stable-Commit-Id: q-4dmrmqgewe5mzj0fzb2qdk5dja
freshtonic added a commit that referenced this pull request Jul 23, 2026
Review of #424 (finding 1) flagged that `eql_v3_json_entry` — the element a
`->` traversal returns, whose per-entry terms are `hm` XOR `op` — resolved to
JsonLike+Contain, because `ste_vec_domain_type!` leaves `term_json_keys()` at
None. A column mistakenly declared with that type would have type-checked as
supporting `->`/`@>` but not `=`/`<`, which is inverted.

Exclude the SteVec sub-structural domains (`NON_COLUMN_DOMAINS`) from the
column catalog so such a column falls through to a native (plaintext) column
instead of a wrong capability set. The every_public_column_domain assertion
skips the same list. Makes the misclassification impossible rather than
latent.

Stable-Commit-Id: q-4dg749ehycbdazr4etn9rk1fvm
@freshtonic
freshtonic force-pushed the queue/eql-v3/typecheck branch from 1a81cf2 to 4b016ae Compare July 23, 2026 11:42
freshtonic added a commit that referenced this pull request Jul 23, 2026
The queue changes user-visible behaviour (v3 typed jsonb domains replace
eql_v2_encrypted, cipherstash-client 0.42.0 / EQL 3.0.2, LIKE/ILIKE now
capability-checked, `@@` newly supported) but touched no CHANGELOG entry.
Add one [Unreleased] section covering the migration, per CLAUDE.md and the
review notes on #424/#428.

Stable-Commit-Id: q-0s2fx8fb1q2563t7b45x9crjmf
@freshtonic

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — went through all six findings. Summary of what changed (all pushed):

1. eql_v3_json_entry classified with inverted semanticsFixed. eql_v3_json_entry (the -> result element, SteVecEntry) is now excluded from the column-domain catalog via a NON_COLUMN_DOMAINS denylist, so resolve() returns None for it and a column mistakenly declared with that type falls through to a native column instead of JsonLike + Contain. The every_public_column_domain_resolves_to_a_known_token_type assertion skips the same list, and a new test pins that it no longer resolves while the real JSON column domains (eql_v3_json, eql_v3_json_search) still do. → fix(proxy): exclude eql_v3_json_entry from the column-domain catalog

2. DomainIdentity::canonical collides with real domainsDone (the doc route you suggested). The doc comment now states the synthesised name may collide with an unrelated real domain that means something else (with the canonical(Text, {json_like}) → eql_v3_text_search example), and that only from_domain_name is authoritative. → docs(eql-mapper): warn that DomainIdentity::canonical may collide with a real domain

3. Test-fixture footgun from the new identity fieldAcknowledged, no change. It's loud-not-silent, as you note; leaving it as-is for whoever writes the transform tests next.

4. docs: is the wrong commit typeNot reworded. That's a message rewrite across the stack; leaving it for the merge rather than rebasing the whole queue for the type prefix.

5. v2 columns → native with only a warn!Done. The warning now spells out that the column is served as a PLAINTEXT (native) column — Proxy runs no encrypt/decrypt on it, new writes are stored in plaintext, migrate before writing — instead of the softer "ignoring unsupported column". The deeper AS ASSIGNMENT write-path concern is inherent to having a v2 column on a v3 build (deliberately native+warn), so I made the warning impossible to miss rather than changing that behaviour. → feat(proxy): make the v2-column plaintext warning ops-visible

6. No CHANGELOG entryDone (against the queue, per your framing). Added an [Unreleased] section covering the whole v3 migration; it lives on #428.

@freshtonic freshtonic changed the title [3/6] docs: handoff for the EQL v3 type checker design [3/6] feat: EQL v3 type checker driven by per-column domain capability Jul 23, 2026
Captures the EQL v3 domain model, three impact maps over eql-mapper
(type system, SQL surface, declarations/macros), and the open design
questions, so the design session starts fresh with full context.

Records one corrected finding: literals are inference sinks
(value.rs:19 unifies them with a fresh tvar, not Native), so a token
type on EqlValue buys no literal checking. Two subagents disagreed on
this; resolved by reading the code.

Stable-Commit-Id: q-2r73hgg9ex95aba238a8fbe4kp
Captures the shared understanding from the grill-with-docs session on
extending the EQL Mapper type checker for EQL v3.

CONTEXT.md: refresh the glossary to the v3 target vocabulary — EqlValue
now carries domain identity; EqlTrait drops Contain and is documented as
coarse; add term-extraction function, functional-index rewrite, query
operand, token type, and domain identity. The old 'known model gap' note
becomes 'design decisions in flight' pointing at the ADRs.

ADRs (new packages/eql-mapper/docs/adr/):
- 0001 functional-index rewrite (term functions, not native operators) —
  forced by Supabase's lack of CREATE OPERATOR; term-fn selection is the
  capability check.
- 0002 token type as inert identity, not a checked dimension — it buys no
  safety (encrypted cols never unify; literals are inference sinks).

Assessed against EQL/eql-bindings 3.0.2 (released 2026-07-20): additive
query-twin bindings only; none of the design decisions change.

Stable-Commit-Id: q-06b9k91e7pnntayq3rs11tqznz
…rands

Two latent bugs in the trait-bound machinery, harmless only while bound
checking is dead code (every encrypted column currently gets
EqlTraits::all()). Fix them before the EQL v3 loader makes bounds
reachable.

- EqlTraits::difference was implemented as XOR (symmetric difference), so
  UnsatisfiedBounds would list traits the type *has* alongside the ones it
  is missing. It is now a true set difference (self AND NOT other).

- Type::must_implement passed its operands reversed relative to
  Unifier::satisfy_bounds (implemented.difference(required) instead of
  required.difference(implemented)). Harmless only because XOR is
  commutative; wrong the moment difference is corrected. Now consistent.

Adds unit tests pinning the asymmetric set-difference semantics and that
must_implement reports exactly the missing bounds.

Refs CIP-3595.

Stable-Commit-Id: q-0e9kbc03hrqef307fg7qb5sqj1
Groundwork for the EQL v3 type checker (ADR-0002). Introduces the token
type and its inert per-column domain identity, threading it through the
type carriers without making it a checked dimension of unification.

- New `TokenType` (integer/text/timestamp/…) and `DomainIdentity`
  (token type + v3 domain typname, e.g. eql_v3_text_ord_ore).
- `EqlValue` widens to (TableColumn, Option<DomainIdentity>, EqlTraits);
  `ColumnKind::Eql`, `SchemaTableColumn` and the new
  `Column::eql_with_identity` carry the identity. `Column::eql` stays as a
  back-compat constructor that defaults the identity to None.
- The identity is `None` everywhere for now: the branch still emits the
  v2 surface and no loader populates it yet (CIP-3598 does). It rides
  through unification and the associated-type machinery untouched, exactly
  because it is never inspected there.
- ColumnKind loses `Copy` (DomainIdentity is not Copy); the handful of
  by-value uses now clone.
- Also fixes the never-invoked, latently-broken no-bounds arm of the
  concrete_ty!/EQL() macro (wrong EqlValue arity, missing .into()).

eql-mapper suite 79 passing; workspace checks clean; fmt + clippy clean.

Refs CIP-3597.

Stable-Commit-Id: q-5dxadzsggt5seabqhg3sdj8ht5
Implements the SchemaManager side of the v3 type checker (ADR-0002):
per-column capability becomes *observed* from the Postgres domain name
instead of a hardcoded EqlTraits::all().

- New schema/eql_domains.rs inverts the eql-bindings v3 catalog
  (v3::all()) into `typname -> (TokenType, EqlTraits)`. Because the
  catalog is generated from the same source as the installed SQL domains,
  the mapping cannot drift from them. Term -> capability: hm->Eq,
  op/ob->Ord, bf->TokenMatch, empty->storage-only; the JSON SteVec
  domains (term_json_keys == None) map to JsonLike (provisional, see note).
- load_schema now resolves each column's v3 domain identity and traits and
  builds the column via Column::eql_with_identity. The legacy
  eql_v2_encrypted composite-type arm is retained for reading pre-v3
  schemas; unrecognised domains load as Native.
- select_table_schemas.sql now also selects information_schema
  domain_name: v3 encrypted columns are jsonb-backed DOMAINs, so udt_name
  reports the base type (jsonb) and the domain typname is only available
  via domain_name (cf. CIP-3441).
- Adds eql-bindings 3.0.1 as a workspace dependency, used only by the
  proxy's schema loader; eql-mapper stays wire-format-agnostic. Re-exports
  DomainIdentity / TokenType from eql-mapper's crate root.

Unit-tested offline against the catalog (12 tests asserting the SEM-term
table, incl. the text hm+ord exception and query-twin exclusion).
End-to-end validation needs a database with EQL v3 installed and is
deferred; the encrypt-config cross-check is a follow-up within CIP-3598.

Refs CIP-3598.

Stable-Commit-Id: q-6p9ykbew5b6sjaf5p7y9qza4ft
…+ Contain

Applies three confirmed design decisions on the EQL v3 type checker.

1. Domain identity is non-optional (strict ADR-0002). EqlValue is now
   (TableColumn, DomainIdentity, EqlTraits) and ColumnKind::Eql carries a
   mandatory DomainIdentity — no more Option. The schema loader always
   supplies the real identity; there is no honest v3 identity for a legacy
   eql_v2_encrypted column, so the loader now drops the v2 arm and logs a
   warning instead of fabricating one (v2 is already retired on this build).
   - New helpers: TokenType::{as_domain_str, from_domain_name},
     DomainIdentity::{from_domain_name, canonical}, and a test-only
     EqlValue::with_canonical_identity.
   - The schema!/concrete_ty!/test-helper macros synthesise a canonical
     text-token identity by default; schema! also gains an
     EQL("<domain>") form to pin an explicit v3 domain (token + OPE/ORE
     variant) for tests that care.

2. JSON domains map to JsonLike + Contain, verified against the installed
   v3 SQL (cipherstash-encrypt.sql): -> / ->> and @> / <@ are real
   operators on eql_v3_json_search (not raise-stubs). Corrects the earlier
   JsonLike-only guess.

3. The encrypt-config cross-check is dropped: the domain name is the sole
   authority for a column's capability. (ADR-0002 doc update to follow.)

The Contain trait is therefore retained (scoped to JSON), NOT deleted as
the handoff/glossary/ticket assumed — @>/<@ raise only on scalar encrypted
columns. Docs/tickets updated separately.

eql-mapper 79 passing; proxy eql_domains 12 passing; workspace check,
fmt, clippy all clean.

Refs CIP-3597, CIP-3598.

Stable-Commit-Id: q-31sxsrtyrptajjpj0n383cb690
…h a real domain

Review of the v3 domain-identity work (#424) noted that canonical() can
synthesise a name that is not merely approximate but identical to an
unrelated real catalog domain — canonical(Text, {json_like}) yields
eql_v3_text_search, whose terms are hm/op/bf (Eq+Ord+TokenMatch), nothing
to do with JSON. Strengthen the doc comment so no caller mistakes the
synthesised name for the column's real domain; only from_domain_name is
authoritative.

Stable-Commit-Id: q-1ewbmpx74jnyxfd7dyftv08ka9
Review of #424 (finding 5) noted the v2->native fallback should be hard to
miss in ops, because a v2 column served as a native passthrough takes no
encryption on the write path: a plaintext value written to it is stored in
plaintext (the eql_v2_encrypted AS ASSIGNMENT cast does no validation).

Spell that out in the warning — the column is served as PLAINTEXT, Proxy
does no encrypt/decrypt on it, migrate before writing — instead of the
softer "ignoring unsupported column".

Stable-Commit-Id: q-4dmrmqgewe5mzj0fzb2qdk5dja
Review of #424 (finding 1) flagged that `eql_v3_json_entry` — the element a
`->` traversal returns, whose per-entry terms are `hm` XOR `op` — resolved to
JsonLike+Contain, because `ste_vec_domain_type!` leaves `term_json_keys()` at
None. A column mistakenly declared with that type would have type-checked as
supporting `->`/`@>` but not `=`/`<`, which is inverted.

Exclude the SteVec sub-structural domains (`NON_COLUMN_DOMAINS`) from the
column catalog so such a column falls through to a native (plaintext) column
instead of a wrong capability set. The every_public_column_domain assertion
skips the same list. Makes the misclassification impossible rather than
latent.

Stable-Commit-Id: q-4dg749ehycbdazr4etn9rk1fvm
Two design corrections found during implementation:

- Containment is NOT removed in v3. Verified against the installed
  cipherstash-encrypt.sql: @>/<@ raise on scalar encrypted columns but are
  real, supported operators on encrypted JSON (eql_v3_json_search). The
  Contain trait is retained as a JSON-only capability. Fixes the glossary
  ("removed in v3") and adds a consequence to ADR-0001.

- ADR-0002 amended: the domain name is the sole authority for a column's
  capability (no encrypt-config cross-check), and the domain identity is
  non-optional (legacy eql_v2_encrypted columns are dropped with a warning
  rather than given a fabricated identity).

Refs CIP-3597, CIP-3598, CIP-3599.

Stable-Commit-Id: q-4rd3p778qhjfc5dessqeeg1b2e
@freshtonic
freshtonic force-pushed the queue/eql-v3/upgrade-deps branch from 21495ac to 9c78564 Compare July 23, 2026 12:12
@freshtonic
freshtonic force-pushed the queue/eql-v3/typecheck branch from 4b016ae to 3fd4309 Compare July 23, 2026 12:12
freshtonic added a commit that referenced this pull request Jul 23, 2026
The queue changes user-visible behaviour (v3 typed jsonb domains replace
eql_v2_encrypted, cipherstash-client 0.42.0 / EQL 3.0.2, LIKE/ILIKE now
capability-checked, `@@` newly supported) but touched no CHANGELOG entry.
Add one [Unreleased] section covering the migration, per CLAUDE.md and the
review notes on #424/#428.

Stable-Commit-Id: q-0s2fx8fb1q2563t7b45x9crjmf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants