familia 2.11.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +15 -0
  3. data/.github/workflows/ci.yml +1 -1
  4. data/.github/workflows/claude-code-review.yml +33 -9
  5. data/.github/workflows/claude.yml +9 -2
  6. data/.github/workflows/code-smells.yml +2 -2
  7. data/.github/workflows/codeql.yml +101 -0
  8. data/.github/workflows/release-gem.yml +5 -5
  9. data/.github/workflows/ruby-lint.yml +66 -0
  10. data/.github/workflows/yardoc.yml +110 -0
  11. data/.yardopts +1 -1
  12. data/CHANGELOG.rst +116 -0
  13. data/Gemfile +3 -3
  14. data/Gemfile.lock +12 -20
  15. data/Rakefile +10 -0
  16. data/docs/guides/feature-encrypted-fields.md +41 -11
  17. data/docs/investigation/memory-audit.md +465 -0
  18. data/docs/overview.md +1 -1
  19. data/examples/encryption_upgrade_proof/.gitignore +3 -0
  20. data/examples/encryption_upgrade_proof/README.md +84 -0
  21. data/examples/encryption_upgrade_proof/common.rb +83 -0
  22. data/examples/encryption_upgrade_proof/gemfiles/Gemfile.dev-no-libsodium +4 -0
  23. data/examples/encryption_upgrade_proof/gemfiles/Gemfile.dev-with-libsodium +5 -0
  24. data/examples/encryption_upgrade_proof/gemfiles/Gemfile.production-today +4 -0
  25. data/examples/encryption_upgrade_proof/model.rb +32 -0
  26. data/examples/encryption_upgrade_proof/phase0_production_today.rb +100 -0
  27. data/examples/encryption_upgrade_proof/phase1_gem_upgrade_no_libsodium.rb +99 -0
  28. data/examples/encryption_upgrade_proof/phase2_libsodium_enabled.rb +297 -0
  29. data/examples/encryption_upgrade_proof/phase3_rollback_hazard.rb +56 -0
  30. data/examples/encryption_upgrade_proof/run.sh +60 -0
  31. data/lib/familia/data_type/database_commands.rb +8 -1
  32. data/lib/familia/data_type/types/stringkey.rb +10 -3
  33. data/lib/familia/encryption/provider.rb +9 -0
  34. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +4 -0
  35. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -0
  36. data/lib/familia/encryption/registry.rb +47 -5
  37. data/lib/familia/encryption.rb +19 -37
  38. data/lib/familia/features/encrypted_fields/concealed_string.rb +27 -0
  39. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +21 -3
  40. data/lib/familia/features/encrypted_fields.rb +27 -4
  41. data/lib/familia/verifiable_identifier.rb +24 -11
  42. data/lib/familia/version.rb +1 -1
  43. data/try/bug_fixes/stringkey_exists_try.rb +153 -0
  44. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +15 -24
  45. data/try/features/encrypted_fields/per_field_algorithm_try.rb +164 -0
  46. data/try/features/encryption/algorithm_upgrade_try.rb +116 -0
  47. data/try/features/encryption/registry_algorithm_resolution_try.rb +125 -0
  48. data/try/integration/verifiable_identifier_try.rb +66 -2
  49. data/try/investigation/memory_leak_proof.rb +415 -0
  50. data/try/investigation/process_memory_leak_proof.rb +205 -0
  51. data/try/unit/data_types/enumerable_consistency/large_scale_consistency_try.rb +34 -8
  52. metadata +24 -2
  53. data/.github/workflows/docs.yml +0 -87
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c322fb98599ca1596829b047f78d414a3216a4e94a61ded4c6d3a4355dd7780a
4
- data.tar.gz: a0af405d479ba7547b2fe65fe17e7a9b0fc5fb04cfe8b5563ce10726187d0396
3
+ metadata.gz: 515d7050ae261290c4e85c6757a68edaa2aae60e21e05b9a6d1fd0457d90fad5
4
+ data.tar.gz: 653efc3da9a21910e7e071ae3065ab06e730334226d8f4820c0ed692fbec8453
5
5
  SHA512:
6
- metadata.gz: ac3aac1967a9730670b6f43f464c001d6c0acad7e599e528d94edb5b60b7939fb59dc5ef320962bfaacd1c204603e36d09647670f2511320a98c9b4fc9f0d715
7
- data.tar.gz: f5f7e14e48be039962af4bcda0ac6c61248143353f773239c5b4ac0a9e9af498f64618d2f18dc48f903c7349d47e375f6e87c6ed686d7e0bbc6ebb444a4456b7
6
+ metadata.gz: 9e82443e034e2a2457bdc5366edcc18586d5df1f1ef4e60dc8c50d3d7431850c8899c42e15200d8ad0638455f13f2024a357b5e904f424fc9315312194329cce
7
+ data.tar.gz: 5eed6b2a2017510bac669b43602a1960b0719fa839eb8e61114be27df7010ea4d90eb73abdefc202b86e6c05d0e9dc674087e90e8c828dd649aaad4c446d38a4
@@ -0,0 +1,15 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "github-actions"
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
12
+ - package-ecosystem: "bundler"
13
+ directory: "/" # Location of package manifests
14
+ schedule:
15
+ interval: "weekly"
@@ -41,7 +41,7 @@ jobs:
41
41
  - 2525:6379
42
42
 
43
43
  steps:
44
- - uses: actions/checkout@v4
44
+ - uses: actions/checkout@v7
45
45
  - name: Set up Ruby
46
46
  uses: ruby/setup-ruby@v1
47
47
  with:
@@ -4,14 +4,34 @@ on:
4
4
  pull_request:
5
5
  types: [opened, synchronize, labeled]
6
6
  workflow_dispatch:
7
+ inputs:
8
+ model:
9
+ description: "Claude model for this run (overrides the CLAUDE_MODEL repo variable)"
10
+ type: choice
11
+ required: false
12
+ default: claude-opus-4-6
13
+ options:
14
+ - claude-opus-4-6
15
+ - claude-sonnet-4-6
16
+ - claude-haiku-4-5-20251001
7
17
 
8
18
  jobs:
9
19
  claude-review:
10
- # Run on opened/synchronize, but only on 'labeled' if label is 'claude-review'
11
- if: |
12
- github.event.action == 'opened' ||
13
- github.event.action == 'synchronize' ||
14
- (github.event.action == 'labeled' && github.event.label.name == 'claude-review')
20
+ # Run only when ALL of the following hold:
21
+ # 1. Author is not a bot (renovate, dependabot, etc. surface as 'name[bot]').
22
+ # 2. PR head is not from a fork. Fork PRs run without repository secrets, so
23
+ # CLAUDE_CODE_OAUTH_TOKEN is empty and the action would fail.
24
+ # 3. The event is a freshly opened PR, a 'claude-review' label add, or a push
25
+ # to a PR that already carries the 'claude-review' label.
26
+ if: ${{
27
+ !endsWith(github.actor, '[bot]') &&
28
+ !github.event.pull_request.head.repo.fork &&
29
+ (
30
+ (github.event.action == 'opened') ||
31
+ (github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
32
+ (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
33
+ )
34
+ }}
15
35
 
16
36
  runs-on: ubuntu-latest
17
37
  permissions:
@@ -22,7 +42,7 @@ jobs:
22
42
 
23
43
  steps:
24
44
  - name: Checkout repository
25
- uses: actions/checkout@v4
45
+ uses: actions/checkout@v7
26
46
  with:
27
47
  fetch-depth: 1
28
48
 
@@ -32,6 +52,13 @@ jobs:
32
52
  with:
33
53
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
34
54
 
55
+ # Primary model precedence: the workflow_dispatch "model" input (manual
56
+ # runs) -> the CLAUDE_MODEL repo variable -> this built-in default.
57
+ # Pinning a current id avoids the action's frozen default, which 404s
58
+ # ("model: claude-sonnet-4-20250514"). Fall back to Sonnet on overload.
59
+ model: "${{ inputs.model || vars.CLAUDE_MODEL || 'claude-opus-4-6' }}"
60
+ fallback_model: "${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}"
61
+
35
62
  # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
36
63
  use_sticky_comment: true
37
64
 
@@ -50,6 +77,3 @@ jobs:
50
77
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
51
78
  # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
52
79
  allowed_tools: "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"
53
-
54
- # Allow bot-initiated workflows (e.g., qodo, dependabot)
55
- allowed_bots: "*"
@@ -26,16 +26,23 @@ jobs:
26
26
  actions: read # Required for Claude to read CI results on PRs
27
27
  steps:
28
28
  - name: Checkout repository
29
- uses: actions/checkout@v4
29
+ uses: actions/checkout@v7
30
30
  with:
31
31
  fetch-depth: 1
32
32
 
33
33
  - name: Run Claude Code
34
34
  id: claude
35
- uses: anthropics/claude-code-action@v1
35
+ uses: anthropics/claude-code-action@beta
36
36
  with:
37
37
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
38
 
39
+ # Primary model: set the CLAUDE_MODEL repo variable to override without
40
+ # editing this file. Pinning a current id avoids the action's frozen
41
+ # default, which 404s ("model: claude-sonnet-4-20250514"). Fall back to
42
+ # Sonnet if the primary is unavailable or overloaded.
43
+ model: "${{ vars.CLAUDE_MODEL || 'claude-opus-4-6' }}"
44
+ fallback_model: "${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}"
45
+
39
46
  # This is an optional setting that allows Claude to read CI results on PRs
40
47
  additional_permissions: |
41
48
  actions: read
@@ -21,7 +21,7 @@ jobs:
21
21
 
22
22
  steps:
23
23
  - name: Checkout code
24
- uses: actions/checkout@v4
24
+ uses: actions/checkout@v7
25
25
 
26
26
  - name: Set up Ruby
27
27
  uses: ruby/setup-ruby@v1
@@ -91,7 +91,7 @@ jobs:
91
91
 
92
92
  steps:
93
93
  - name: Checkout code
94
- uses: actions/checkout@v4
94
+ uses: actions/checkout@v7
95
95
 
96
96
  - name: Set up Ruby
97
97
  uses: ruby/setup-ruby@v1
@@ -0,0 +1,101 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL Advanced"
13
+
14
+ on:
15
+ push:
16
+ branches: [ "main" ]
17
+ pull_request:
18
+ branches: [ "main" ]
19
+ schedule:
20
+ - cron: '30 0 * * 6'
21
+
22
+ jobs:
23
+ analyze:
24
+ name: Analyze (${{ matrix.language }})
25
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
26
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
27
+ # - https://gh.io/supported-runners-and-hardware-resources
28
+ # - https://gh.io/using-larger-runners (GitHub.com only)
29
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
30
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31
+ permissions:
32
+ # required for all workflows
33
+ security-events: write
34
+
35
+ # required to fetch internal or private CodeQL packs
36
+ packages: read
37
+
38
+ # only required for workflows in private repositories
39
+ actions: read
40
+ contents: read
41
+
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ include:
46
+ - language: actions
47
+ build-mode: none
48
+ - language: ruby
49
+ build-mode: none
50
+ # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
51
+ # Use `c-cpp` to analyze code written in C, C++ or both
52
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
58
+ steps:
59
+ - name: Checkout repository
60
+ uses: actions/checkout@v7
61
+
62
+ # Add any setup steps before running the `github/codeql-action/init` action.
63
+ # This includes steps like installing compilers or runtimes (`actions/setup-node`
64
+ # or others). This is typically only required for manual builds.
65
+ # - name: Setup runtime (example)
66
+ # uses: actions/setup-example@v1
67
+
68
+ # Initializes the CodeQL tools for scanning.
69
+ - name: Initialize CodeQL
70
+ uses: github/codeql-action/init@v4
71
+ with:
72
+ languages: ${{ matrix.language }}
73
+ build-mode: ${{ matrix.build-mode }}
74
+ # If you wish to specify custom queries, you can do so here or in a config file.
75
+ # By default, queries listed here will override any specified in a config file.
76
+ # Prefix the list here with "+" to use these queries and those in the config file.
77
+
78
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
79
+ # queries: security-extended,security-and-quality
80
+
81
+ # If the analyze step fails for one of the languages you are analyzing with
82
+ # "We were unable to automatically build your code", modify the matrix above
83
+ # to set the build mode to "manual" for that language. Then modify this step
84
+ # to build your code.
85
+ # ℹ️ Command-line programs to run using the OS shell.
86
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
87
+ - name: Run manual build steps
88
+ if: matrix.build-mode == 'manual'
89
+ shell: bash
90
+ run: |
91
+ echo 'If you are using a "manual" build mode for one or more of the' \
92
+ 'languages you are analyzing, replace this with the commands to build' \
93
+ 'your code, for example:'
94
+ echo ' make bootstrap'
95
+ echo ' make release'
96
+ exit 1
97
+
98
+ - name: Perform CodeQL Analysis
99
+ uses: github/codeql-action/analyze@v4
100
+ with:
101
+ category: "/language:${{matrix.language}}"
@@ -124,7 +124,7 @@ jobs:
124
124
 
125
125
  steps:
126
126
  - name: Checkout
127
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
127
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
128
128
  with:
129
129
  persist-credentials: false
130
130
 
@@ -132,10 +132,10 @@ jobs:
132
132
  uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
133
133
  with:
134
134
  bundler-cache: true
135
- # Pinned to 3.2, the oldest non-experimental Ruby in ci.yml's matrix
136
- # and the gemspec's required_ruby_version floor, so the release build
137
- # matches the minimum supported configuration that CI exercises.
138
- ruby-version: "3.2"
135
+ # Pinned to 3.3 to match the otto/rhales release builds. The gem is
136
+ # pure Ruby, so the build Ruby is independent of the supported range
137
+ # declared in the gemspec (>= 3.2).
138
+ ruby-version: "3.3"
139
139
 
140
140
  - name: Verify release tag matches Familia::VERSION
141
141
  env:
@@ -0,0 +1,66 @@
1
+ # .github/workflows/ruby-lint.yml
2
+
3
+ name: Ruby Lint
4
+
5
+ # Runs RuboCop across the supported Ruby versions. Linting is informational:
6
+ # every dependency/lint step is continue-on-error, so style findings surface as
7
+ # annotations without blocking merges.
8
+
9
+ on:
10
+ push:
11
+ branches:
12
+ - fix/*
13
+ - rel/*
14
+ pull_request:
15
+ branches:
16
+ - main
17
+ - develop
18
+ - feature/*
19
+ workflow_dispatch:
20
+ inputs:
21
+ debug_enabled:
22
+ type: boolean
23
+ description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
24
+ required: false
25
+ default: false
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ lint:
32
+ timeout-minutes: 10 # prevent hung jobs
33
+
34
+ runs-on: ubuntu-24.04
35
+
36
+ strategy:
37
+ fail-fast: true
38
+ matrix:
39
+ ruby: ['3.2', '3.3', '3.4', '3.5', '4.0']
40
+ continue-on-error: [true]
41
+
42
+ steps:
43
+ - name: Checkout code
44
+ uses: actions/checkout@v7
45
+
46
+ - uses: ruby/setup-ruby@v1
47
+ with:
48
+ ruby-version: ${{ matrix.ruby }}
49
+ bundler-cache: true
50
+
51
+ - name: Setup tmate session
52
+ uses: mxschmitt/action-tmate@7b6a61a73bbb9793cb80ad69b8dd8ac19261834c # v3
53
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
54
+ with:
55
+ detached: true
56
+
57
+ - name: Install dependencies
58
+ continue-on-error: ${{ matrix.continue-on-error }}
59
+ run: |
60
+ bundle config path vendor/bundle
61
+ bundle install --jobs 4 --retry 3
62
+
63
+ - name: Run Rubocop
64
+ continue-on-error: ${{ matrix.continue-on-error }}
65
+ run: |
66
+ bundle exec rubocop --config .rubocop.yml --format json --fail-level warning
@@ -0,0 +1,110 @@
1
+ name: Generate and Deploy YARD Documentation
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'lib/**/*'
9
+ - 'docs/**/*'
10
+ - 'README.md'
11
+ - 'CHANGELOG.md'
12
+ - 'CHANGELOG.rst'
13
+ - 'LICENSE.txt'
14
+ - '.yardopts'
15
+ - '.github/workflows/yardoc.yml'
16
+ workflow_dispatch:
17
+
18
+ permissions:
19
+ contents: read
20
+ pages: write
21
+ id-token: write
22
+
23
+ # Allow only one concurrent deployment, skipping runs queued between the run
24
+ # in-progress and latest queued. Do NOT cancel in-progress runs so production
25
+ # deployments can complete.
26
+ concurrency:
27
+ group: "pages"
28
+ cancel-in-progress: false
29
+
30
+ jobs:
31
+ build-docs:
32
+ timeout-minutes: 10
33
+ runs-on: ubuntu-latest
34
+ name: Generate YARD Documentation
35
+
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v7
39
+ with:
40
+ fetch-depth: 0
41
+
42
+ - name: Set up Ruby environment
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: '3.4'
46
+ bundler-cache: true
47
+
48
+ - name: Generate documentation
49
+ # Uses the repository's committed .yardopts as the single source of
50
+ # truth for the output dir, includes/excludes, markup, and tags.
51
+ run: |
52
+ echo "::group::YARD Documentation Generation"
53
+ bundle exec yard stats --list-undoc || true
54
+ bundle exec yard doc
55
+ echo "::endgroup::"
56
+
57
+ - name: Disable Jekyll processing
58
+ # YARD emits files and directories that begin with underscores; the
59
+ # .nojekyll marker stops GitHub Pages from stripping them.
60
+ run: touch doc/.nojekyll
61
+
62
+ - name: Validate documentation output
63
+ run: |
64
+ echo "::group::Documentation Validation"
65
+ if [ ! -d "doc" ]; then
66
+ echo "Error: documentation directory 'doc' was not generated." >&2
67
+ echo "Ensure .yardopts sets '--output-dir doc'." >&2
68
+ exit 1
69
+ fi
70
+ if [ ! -f "doc/index.html" ]; then
71
+ echo "Warning: doc/index.html not found"
72
+ fi
73
+ echo "Generated HTML files: $(find doc -name '*.html' | wc -l)"
74
+ echo "Total documentation size: $(du -sh doc/ | cut -f1)"
75
+ echo "::endgroup::"
76
+
77
+ - name: Setup GitHub Pages configuration
78
+ uses: actions/configure-pages@v4
79
+
80
+ - name: Upload documentation artifact
81
+ uses: actions/upload-pages-artifact@v5
82
+ with:
83
+ path: './doc'
84
+
85
+ deploy-pages:
86
+ timeout-minutes: 10
87
+ environment:
88
+ name: github-pages
89
+ url: ${{ steps.deployment.outputs.page_url }}
90
+ runs-on: ubuntu-latest
91
+ needs: build-docs
92
+ outputs:
93
+ page_url: ${{ steps.deployment.outputs.page_url }}
94
+
95
+ steps:
96
+ - name: Deploy to GitHub Pages
97
+ id: deployment
98
+ uses: actions/deploy-pages@v5
99
+
100
+ notify-completion:
101
+ timeout-minutes: 5
102
+ runs-on: ubuntu-latest
103
+ needs: [build-docs, deploy-pages]
104
+ if: success()
105
+
106
+ steps:
107
+ - name: Documentation deployment summary
108
+ run: |
109
+ echo "::notice title=Documentation Deployed::YARD documentation successfully deployed to GitHub Pages"
110
+ echo "::notice title=Access URL::Documentation available at: ${{ needs.deploy-pages.outputs.page_url }}"
data/.yardopts CHANGED
@@ -6,7 +6,7 @@
6
6
  --charset utf-8
7
7
  --markup markdown
8
8
  --markup-provider redcarpet
9
- --output-dir public
9
+ --output-dir doc
10
10
  --embed-mixin ClassMethods
11
11
  --embed-mixin InstanceMethods
12
12
  --embed-mixin Features
data/CHANGELOG.rst CHANGED
@@ -7,6 +7,122 @@ The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`
7
7
 
8
8
  <!--scriv-insert-here-->
9
9
 
10
+ .. _changelog-2.11.1:
11
+
12
+ 2.11.1 — 2026-07-04
13
+ ===================
14
+
15
+ Added
16
+ -----
17
+
18
+ - ``encrypted_field`` now honors a per-field ``algorithm:`` option, pinning that
19
+ field's write algorithm to a specific registered provider (``'aes-256-gcm'`` or
20
+ ``'xchacha20poly1305'``) independent of the registry's default-provider
21
+ priority. The option was previously documented but silently ignored, so writes
22
+ always used the default provider. Decryption stays envelope-driven, so a pin can
23
+ be added, changed, or removed without breaking ciphertext already at rest, and
24
+ ``re_encrypt_fields!`` re-encrypts under the pin rather than the default. This is
25
+ the supported lever for a reader-before-writer format migration: deploy
26
+ ``rbnacl`` fleet-wide so every node can *read* XChaCha20-Poly1305 while keeping
27
+ *writes* pinned to AES-256-GCM until all readers are confirmed capable, then drop
28
+ the pin. Issue #334
29
+
30
+ Changed
31
+ -------
32
+
33
+ - ``Familia::Encryption::Registry.get`` now distinguishes an unknown algorithm
34
+ from a *known* algorithm whose provider is not available on the current node.
35
+ Because ``Registry.register`` only stores providers whose runtime dependency is
36
+ present, pinning ``encrypted_field ..., algorithm: 'xchacha20poly1305'`` on a
37
+ node without ``rbnacl``/libsodium previously raised the misleading
38
+ ``"Unsupported algorithm: xchacha20poly1305"`` -- pointing an operator at a typo
39
+ when the real fix is a missing dependency. It now names the provider, explains
40
+ the dependency is missing, and (because ``get`` runs on both the encrypt and
41
+ decrypt paths) states that installing the dependency is what enables reading
42
+ *and* writing the algorithm, framing an algorithm pin as a write-time
43
+ workaround that cannot decrypt existing ciphertext. Each provider declares its
44
+ own dependency via a new ``Provider.dependency_hint`` class method (nil for
45
+ always-available providers like OpenSSL AES-256-GCM), so the generic error path
46
+ names the correct library as providers are added rather than hardcoding any one
47
+ of them. The set of registerable providers is centralized in
48
+ ``Registry.known_providers``, the single source of truth shared by ``setup!``
49
+ and ``get``. Error-message and internal-refactor only; the resolution of every
50
+ available algorithm is unchanged. Issue #334
51
+
52
+ Fixed
53
+ -----
54
+
55
+ - ``Familia::DataType#exists?`` no longer returns ``true`` for a deleted or
56
+ never-created scalar key (``StringKey``, ``Counter``, ``Lock``,
57
+ ``JsonStringKey``). The check was ``dbclient.exists(dbkey) && !size.zero?``,
58
+ but ``EXISTS`` returns an Integer count and ``0`` is truthy in Ruby, so the
59
+ guard never short-circuited on a missing key -- existence was decided
60
+ entirely by the size check. ``exists?`` now uses a boolean-coerced ``EXISTS``
61
+ count directly. Issue #331
62
+
63
+ - Relatedly, ``StringKey#size``/``#length``/``#empty?`` (and ``Lock``'s) no
64
+ longer reflect the never-nil ``#to_s`` fallback. ``#char_count`` derived from
65
+ ``#to_s.size``, and ``#to_s`` intentionally returns ``Familia::Base``'s
66
+ documented "never nil" inspect-string when the value is absent -- so
67
+ ``#size`` was non-zero (and ``#empty?`` false) for a missing key.
68
+ ``#char_count`` now reads ``#value`` directly; ``#to_s`` is left unchanged.
69
+ Issue #331
70
+
71
+ - ``encrypted_fields_status`` now reports each field's real algorithm for a live
72
+ encrypted value (e.g. ``{ encrypted: true, algorithm: "aes-256-gcm", cleared:
73
+ false }``), honoring any per-field pin. Previously it returned ``{ encrypted:
74
+ false, value: "[CONCEALED]" }`` for every encrypted field, because
75
+ ``ConcealedString`` had no ``concealed?`` predicate for the status check to
76
+ match -- so the algorithm shown in the method's docstring and the guides was
77
+ never actually produced. ``ConcealedString`` gains ``#concealed?`` and
78
+ ``#algorithm`` readers (the latter reads the stored envelope). Issue #334
79
+
80
+ Documentation
81
+ -------------
82
+
83
+ - Added an executable, multi-phase proof (``examples/encryption_upgrade_proof/``)
84
+ demonstrating that installing ``rbnacl`` safely flips new writes to
85
+ XChaCha20-Poly1305 while every existing AES-256-GCM envelope — including
86
+ ciphertext written by the released 2.10.1 gem, under the pre-#310 static HKDF
87
+ salt, and under a retired master key version — keeps decrypting. Also pins,
88
+ as deliberately-passing checks, two operational hazards: the XChaCha
89
+ ``encryption_personalization`` cannot be rotated (no history/fallback like
90
+ ``encryption_hkdf_salt_history``), and once any XChaCha envelope exists,
91
+ every node that may read it needs libsodium installed. PR #330
92
+
93
+ - The encrypted-fields guide previously showed a ``provider: :aes_gcm`` field
94
+ option that was never implemented; those examples now use the real
95
+ ``algorithm: 'aes-256-gcm'`` form, and the ``Familia::Encryption`` facade
96
+ docstring documents the shipped behavior instead of a hypothetical
97
+ implementation sketch. The ``encrypted_fields_status`` output examples across
98
+ the guides and the overview were corrected to match what the method now
99
+ returns. Issue #334
100
+
101
+ - Added a memory-audit investigation (``docs/investigation/memory-audit.md``)
102
+ diagnosing #309's ``<collection>_with_permission`` O(N) query as a transient,
103
+ GC-reclaimable spike rather than a per-process leak, and auditing the rest of
104
+ ``lib/`` for per-process growth (concluding Familia has no unconditional leak).
105
+ Ships two executable proofs in ``try/investigation/`` — a pure-Ruby
106
+ ``process_memory_leak_proof.rb`` and a live-Redis ``memory_leak_proof.rb``.
107
+ Diagnosis only; no runtime behaviour is changed by the investigation. Issue #309
108
+
109
+ AI Assistance
110
+ -------------
111
+
112
+ - The encryption upgrade proof, its regression tryouts, and this changelog
113
+ entry were drafted with AI assistance. PR #330
114
+
115
+ - The ``exists?`` fix and its regression tryouts were drafted with AI
116
+ assistance. Issue #331
117
+
118
+ - The per-field algorithm implementation, the ``encrypted_fields_status`` fix,
119
+ their regression tryouts, the guide corrections, and the
120
+ ``Registry.get`` error-message refinement were drafted with AI assistance.
121
+ Issue #334
122
+
123
+ - The memory-audit investigation and its executable proofs were drafted with AI
124
+ assistance. Issue #309
125
+
10
126
  .. _changelog-2.11.0:
11
127
 
12
128
  2.11.0 — 2026-06-22
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
5
5
  gemspec
6
6
 
7
7
  group :test do
8
- gem 'concurrent-ruby', '~> 1.3.6', require: false
8
+ gem 'concurrent-ruby', '~> 1.3.7', require: false
9
9
  gem 'ruby-prof'
10
10
  gem 'stackprof'
11
11
  gem 'timecop', require: false
@@ -21,12 +21,12 @@ group :development, :test do
21
21
  # Config#to_data, which we don't use (we don't use Dry::Configurable at all),
22
22
  # so cap below 1.4 to keep the dev bundle installable on Ruby 3.2.
23
23
  gem 'dry-configurable', '>= 1.3', '< 1.5', require: false
24
- gem 'irb', '~> 1.15.2', require: false
24
+ gem 'irb', '~> 1.18.0', require: false
25
25
  gem 'json_schemer', '~> 2.0', require: false
26
26
  gem 'rake', '~> 13.0', require: false
27
27
  gem 'redcarpet', require: false
28
28
  gem 'reek', require: false
29
- gem 'rubocop', '~> 1.85.1', require: false
29
+ gem 'rubocop', '~> 1.88.0', require: false
30
30
  gem 'rubocop-performance', require: false
31
31
  gem 'rubocop-thread_safety', require: false
32
32
  gem 'ruby-lsp', require: false