Skip to content

Canonical shared encryption config#170

Merged
freshtonic merged 8 commits intomainfrom
refactor/canonical-config
Apr 9, 2026
Merged

Canonical shared encryption config#170
freshtonic merged 8 commits intomainfrom
refactor/canonical-config

Conversation

@freshtonic
Copy link
Copy Markdown
Contributor

@freshtonic freshtonic commented Apr 1, 2026

feat(config): accept canonical type names and plaintext_type alias

Add json, float, decimal, and timestamp as valid cast types. Support plaintext_type as a canonical alias for cast_as in
configuration, with the config() view resolving either field via COALESCE. Fix inverted validation logic in
config_check_cast and ambiguous alias in config().

Summary by CodeRabbit

  • New Features

    • Introduced plaintext_type as the preferred field for specifying decrypted target types; cast_as remains supported and plaintext_type takes precedence
    • Expanded supported types to include json, float, decimal, and timestamp (previous aliases remain accepted)
  • Documentation

    • Updated configuration reference to document plaintext_type and the expanded type list
  • Tests

    • Added integration tests for plaintext_type, precedence, expanded type acceptance, and validation
  • Chores

    • CI workflow env var added; developer tooling Python version bumped to 3.14

@freshtonic freshtonic requested a review from tobyhede April 1, 2026 12:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a preferred plaintext_type field for decrypted target types (with cast_as kept as a deprecated alias and plaintext_type taking precedence), expands accepted types (json, float, decimal, timestamp), and updates validation, config extraction, docs, tests, and CI env/tool versions.

Changes

Cohort / File(s) Summary
Documentation
docs/reference/index-config.md
Documented plaintext_type as preferred, marked cast_as deprecated, updated supported type list (json, float, decimal, timestamp) and noted jsonb remains accepted.
SQL Validation & Constraints
src/config/constraints.sql
eql_v2.config_check_cast(val jsonb) now validates both cast_as and plaintext_type (treating plaintext_type as canonical), expands allowed types whitelist, raises distinct errors per field, requires neither field for empty config, and declares IMMUTABLE STRICT PARALLEL SAFE.
Configuration Functions
src/config/functions.sql
add_search_config() allowlist extended to include json, float, decimal, timestamp; config() now prefers column_config.value->>'plaintext_type' falling back to ->>'cast_as', adjusted CTE aliases and iteration to read per-table configs from tables.tbl_config.
Integration Tests
tests/sqlx/tests/config_tests.rs
Added six SQLx tests covering canonical type acceptance, add_search_config acceptance, plaintext_type support and precedence, mixed-column configs, and invalid plaintext_type rejection.
CI Workflows
.github/workflows/release-eql.yml, .github/workflows/test-eql.yml
Added workflow-level env FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true (and MISE_VERBOSE=1 in release workflow).
Tooling Config
mise.toml
Bumped Python tool version from 3.13 to 3.14.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application/Client
    participant ConfigF as eql_v2.config()
    participant Validator as eql_v2.config_check_cast()
    participant DB as Database

    App->>ConfigF: Request config for table/column
    ConfigF->>DB: SELECT tbl_config FROM eql_v2_configuration
    DB-->>ConfigF: Return config JSON
    ConfigF->>ConfigF: resolved_type = COALESCE(tbl_config->>'plaintext_type', tbl_config->>'cast_as')
    ConfigF->>Validator: Validate(resolved_type)
    Validator->>Validator: Check against allowed types {text,int,small_int,big_int,real,double,boolean,date,jsonb,json,float,decimal,timestamp}
    alt Type Valid
        Validator-->>ConfigF: true
        ConfigF-->>App: Return decrypts_as = resolved_type
    else Type Invalid
        Validator-->>ConfigF: RAISE exception
        ConfigF-->>App: Return configuration error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • coderdan

Poem

🐇 I nibble docs and SQL with glee,

plaintext_type hopped in—preferred and free.
Float, json, decimal, timestamp in view,
Casts step aside when plaintext_type is true.
A joyful hop for configs shiny and new ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Canonical shared encryption config' accurately captures the main objective: introducing canonical cast type names and the plaintext_type alias as a replacement for the deprecated cast_as field.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/canonical-config

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 and usage tips.

@freshtonic freshtonic changed the title Canonical encryption config Canonical shared encryption config Apr 1, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/config/constraints.sql (1)

78-79: Centralize the cast-type allowlist to prevent validator drift.

This list is duplicated here and in src/config/functions.sql (Line 49). If they diverge, add_search_config() and table constraints can disagree on valid input.

♻️ Suggested direction
-    _valid_types text[] := '{text, int, small_int, big_int, real, double, boolean, date, jsonb, json, float, decimal, timestamp}';
+    _valid_types text[] := eql_v2.valid_cast_types();

Add a shared helper (for example in a common SQL file) and reuse it in both validators.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/constraints.sql` around lines 78 - 79, The _valid_types array
declared in constraints.sql is duplicated with the allowlist used by
add_search_config() (in functions.sql), so centralize the list by extracting it
into a single reusable helper (e.g., a SQL function or constant view like
get_allowed_cast_types() or allowed_casts() in a common SQL file) and replace
the local _valid_types declaration in constraints.sql and the hardcoded list in
add_search_config() with calls to that helper; ensure the helper returns the
same text[] or setof text shape required by both the table constraint logic and
add_search_config() so both validators reference the single source of truth.
tests/sqlx/tests/config_tests.rs (1)

503-545: Add a precedence test for cast_as vs plaintext_type.

These tests validate alias support, but they don’t lock in the documented rule that cast_as wins when both are present.

🧪 Suggested test addition
+#[sqlx::test(fixtures(path = "../fixtures", scripts("config_tables")))]
+async fn configuration_prefers_cast_as_over_plaintext_type(pool: PgPool) -> Result<()> {
+    sqlx::query("TRUNCATE TABLE eql_v2_configuration")
+        .execute(&pool)
+        .await?;
+
+    let config = serde_json::json!({
+        "v": 1,
+        "tables": {
+            "users": {
+                "email": {
+                    "cast_as": "int",
+                    "plaintext_type": "text",
+                    "indexes": { "unique": {} }
+                }
+            }
+        }
+    });
+
+    sqlx::query("INSERT INTO eql_v2_configuration (data) VALUES ($1::jsonb)")
+        .bind(&config)
+        .execute(&pool)
+        .await?;
+
+    let decrypts_as: Option<String> = sqlx::query_scalar(
+        "SELECT decrypts_as FROM eql_v2.config() WHERE relation = 'users' AND col_name = 'email'",
+    )
+    .fetch_one(&pool)
+    .await?;
+
+    assert_eq!(decrypts_as.as_deref(), Some("int"));
+    Ok(())
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sqlx/tests/config_tests.rs` around lines 503 - 545, Add a precedence
test (or extend configuration_accepts_plaintext_type_field) to ensure when both
"cast_as" and "plaintext_type" are provided the view returns "cast_as" (i.e.,
cast_as wins): create a config JSON that includes both fields for the same
column (e.g., "email": { "cast_as": "varchar", "plaintext_type": "text", ... }),
insert it the same way using
sqlx::query(...).bind(&config).execute(&pool).await, then query eql_v2.config()
like the existing test and assert decrypts_as equals the cast_as value
("varchar"); reference the existing test function
configuration_accepts_plaintext_type_field and the query selecting decrypts_as
to locate where to add this assertion/test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/plans/2026-04-01-accept-canonical-type-names.md`:
- Line 13: Update the machine-specific home-directory reference in the markdown
line that reads
`~/cipherstash/cipherstash-suite/docs/plans/2026-04-01-canonical-encryption-config-design.md`
to a repo-relative path so others/CI can resolve it (e.g., replace the
`~/cipherstash/cipherstash-suite/...` fragment with
`docs/plans/2026-04-01-canonical-encryption-config-design.md`); locate the
string in docs/plans/2026-04-01-accept-canonical-type-names.md and change it
accordingly.
- Around line 17-277: The markdown file uses inconsistent heading levels
(jumping from # to ###) which triggers markdownlint MD001; update each "Task 1",
"Task 2", etc. section headers currently written as "### Task X: ..." to use "##
Task X: ..." (and adjust any subheadings under them if necessary) so headings
increment properly; ensure all top-level task headings are exactly two hashes
and scan the document for any other heading level jumps to normalize the
hierarchy.

---

Nitpick comments:
In `@src/config/constraints.sql`:
- Around line 78-79: The _valid_types array declared in constraints.sql is
duplicated with the allowlist used by add_search_config() (in functions.sql), so
centralize the list by extracting it into a single reusable helper (e.g., a SQL
function or constant view like get_allowed_cast_types() or allowed_casts() in a
common SQL file) and replace the local _valid_types declaration in
constraints.sql and the hardcoded list in add_search_config() with calls to that
helper; ensure the helper returns the same text[] or setof text shape required
by both the table constraint logic and add_search_config() so both validators
reference the single source of truth.

In `@tests/sqlx/tests/config_tests.rs`:
- Around line 503-545: Add a precedence test (or extend
configuration_accepts_plaintext_type_field) to ensure when both "cast_as" and
"plaintext_type" are provided the view returns "cast_as" (i.e., cast_as wins):
create a config JSON that includes both fields for the same column (e.g.,
"email": { "cast_as": "varchar", "plaintext_type": "text", ... }), insert it the
same way using sqlx::query(...).bind(&config).execute(&pool).await, then query
eql_v2.config() like the existing test and assert decrypts_as equals the cast_as
value ("varchar"); reference the existing test function
configuration_accepts_plaintext_type_field and the query selecting decrypts_as
to locate where to add this assertion/test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3793fb43-4b1c-44a9-bee1-d36dc4680f02

📥 Commits

Reviewing files that changed from the base of the PR and between 68c32ea and 3fffd55.

📒 Files selected for processing (5)
  • docs/plans/2026-04-01-accept-canonical-type-names.md
  • docs/reference/index-config.md
  • src/config/constraints.sql
  • src/config/functions.sql
  • tests/sqlx/tests/config_tests.rs

Add json, float, decimal, and timestamp as valid cast types. Support
plaintext_type as a canonical alias for cast_as in configuration, with
the config() view resolving either field via COALESCE. Fix inverted
validation logic in config_check_cast and ambiguous alias in config().
The COALESCE argument order in eql_v2.config() was checking cast_as
before plaintext_type, meaning the deprecated field would win when
both were present. Swap the order so plaintext_type takes precedence.

Add test verifying precedence behavior and update docs to clarify
plaintext_type is preferred and cast_as is a deprecated alias.
Not needed in the final PR.
@freshtonic freshtonic force-pushed the refactor/canonical-config branch 3 times, most recently from df237ea to 11dba92 Compare April 8, 2026 04:57
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release-eql.yml:
- Around line 15-17: The env block contains an invalid YAML entry using
shell-style assignment for MISE_VERBOSE; update the env mapping so both entries
are valid YAML key: value pairs (e.g., keep FORCE_JAVASCRIPT_ACTIONS_TO_NODE24:
true and change MISE_VERBOSE to a YAML mapping like MISE_VERBOSE: "1" or
MISE_VERBOSE: 1) ensuring proper indentation and no shell-style equals sign.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9d8fb2b-aec7-4a99-b3a3-72a6aba14b3c

📥 Commits

Reviewing files that changed from the base of the PR and between a9c081c and dd6a902.

📒 Files selected for processing (3)
  • .github/workflows/release-eql.yml
  • mise.toml
  • tests/sqlx/tests/config_tests.rs
✅ Files skipped from review due to trivial changes (2)
  • mise.toml
  • tests/sqlx/tests/config_tests.rs

@freshtonic freshtonic force-pushed the refactor/canonical-config branch 3 times, most recently from dcb7228 to d271eed Compare April 8, 2026 06:43
Copy link
Copy Markdown
Contributor

@auxesis auxesis left a comment

Choose a reason for hiding this comment

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

LGTM.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Unit tests committed locally. Commit: 6f422090b4c33c989d388ea242bac7358e300008

@freshtonic freshtonic force-pushed the refactor/canonical-config branch from ac9c030 to d271eed Compare April 8, 2026 12:40
Add test that configures one column with cast_as and another with
plaintext_type to ensure both resolve correctly through eql_v2.config().
Python 3.13.12 installation was missing a lib directory, causing CI
failures. Upgrading to 3.14 resolves the issue.

Also ensure consistent mise GH Action version.
@freshtonic freshtonic force-pushed the refactor/canonical-config branch from d271eed to c40537d Compare April 9, 2026 00:49
@freshtonic freshtonic merged commit c89356f into main Apr 9, 2026
7 checks passed
@freshtonic freshtonic deleted the refactor/canonical-config branch April 9, 2026 01:14
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.

3 participants