familia 2.11.2 → 2.12.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +14 -8
- data/.github/workflows/claude.yml +9 -25
- data/.github/workflows/code-smells.yml +1 -1
- data/.github/workflows/yardoc.yml +1 -1
- data/.gitignore +8 -2
- data/.talismanrc +13 -0
- data/AGENTS.md +7 -0
- data/CHANGELOG.rst +91 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +12 -15
- data/README.md +14 -3
- data/changelog.d/README.md +46 -54
- data/changelog.d/fragments/20260725_202500_delano_282_unsaved_index_guard.md +7 -0
- data/changelog.d/fragments/20260725_213000_delano_282_index_scope_tracker.md +18 -0
- data/changelog.d/fragments/20260728_120000_delano_365_stale_tracker_prune.rst +30 -0
- data/docs/adr/0001-record-architecture-decisions.md +20 -0
- data/docs/adr/0002-watch-for-private-keys-lua-for-shared-keys.md +62 -0
- data/docs/adr/README.md +14 -0
- data/docs/guides/datatype-collections.md +101 -1
- data/docs/guides/encryption.md +42 -7
- data/docs/guides/feature-encrypted-fields.md +2 -0
- data/docs/guides/feature-housekeeping.md +50 -1
- data/docs/guides/feature-relationships-indexing.md +241 -3
- data/docs/guides/feature-relationships-participation.md +14 -0
- data/docs/guides/index.md +10 -7
- data/docs/investigation/memory-audit.md +5 -0
- data/docs/migrating/v2.10.md +14 -0
- data/docs/overview.md +120 -37
- data/docs/reference/api-technical.md +74 -20
- data/docs/schema-validation.md +199 -0
- data/docs/security/2026-07-19-audit.md +96 -0
- data/docs/security/2026-07-30-audit.md +33 -0
- data/docs/transaction_safety.md +215 -0
- data/examples/encryption_upgrade_proof/README.md +8 -5
- data/examples/encryption_upgrade_proof/gemfiles/Gemfile.dev-no-libsodium +1 -0
- data/examples/encryption_upgrade_proof/gemfiles/Gemfile.dev-with-libsodium +1 -0
- data/examples/encryption_upgrade_proof/gemfiles/Gemfile.production-today +1 -0
- data/examples/encryption_upgrade_proof/phase2_libsodium_enabled.rb +22 -7
- data/examples/encryption_upgrade_proof/phase3_rollback_hazard.rb +1 -1
- data/familia.gemspec +0 -2
- data/lib/familia/connection/middleware.rb +4 -2
- data/lib/familia/connection/operations.rb +18 -2
- data/lib/familia/connection/transaction_core.rb +1 -1
- data/lib/familia/connection.rb +49 -3
- data/lib/familia/data_type/collection_base.rb +55 -7
- data/lib/familia/data_type/types/hashkey.rb +150 -0
- data/lib/familia/data_type/types/listkey.rb +89 -18
- data/lib/familia/data_type/types/lock.rb +34 -6
- data/lib/familia/data_type/types/sorted_set.rb +87 -22
- data/lib/familia/data_type.rb +117 -8
- data/lib/familia/encryption/manager.rb +128 -73
- data/lib/familia/encryption/provider.rb +7 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +3 -2
- data/lib/familia/encryption/providers/blake2b_personalization.rb +101 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +24 -13
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +21 -12
- data/lib/familia/errors.rb +38 -1
- data/lib/familia/features/encrypted_fields/concealed_string.rb +14 -15
- data/lib/familia/features/encrypted_fields.rb +5 -3
- data/lib/familia/features/housekeeping/enforce_collection_caps.rb +94 -0
- data/lib/familia/features/housekeeping.rb +20 -9
- data/lib/familia/features/relationships/collection_operations.rb +87 -6
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +49 -8
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +222 -33
- data/lib/familia/features/relationships/indexing.rb +483 -15
- data/lib/familia/features/relationships/participation/target_methods.rb +194 -21
- data/lib/familia/features/relationships/participation.rb +17 -5
- data/lib/familia/features/relationships/participation_relationship.rb +1 -0
- data/lib/familia/features/relationships/score_encoding.rb +109 -28
- data/lib/familia/features/relationships.rb +22 -40
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +9 -3
- data/lib/familia/features/transient_fields.rb +1 -0
- data/lib/familia/field_type.rb +206 -11
- data/lib/familia/horreum/atomic_write.rb +17 -0
- data/lib/familia/horreum/database_commands.rb +21 -3
- data/lib/familia/horreum/definition.rb +0 -111
- data/lib/familia/horreum/management/repair.rb +0 -39
- data/lib/familia/horreum/persistence.rb +907 -117
- data/lib/familia/multi_result.rb +115 -21
- data/lib/familia/settings.rb +47 -14
- data/lib/familia/version.rb +1 -1
- data/lib/middleware/database_logger.rb +54 -7
- data/try/bug_fixes/overview_permission_example_try.rb +62 -0
- data/try/bug_fixes/partial_write_index_maintenance_try.rb +492 -0
- data/try/bug_fixes/permission_query_try.rb +151 -0
- data/try/bug_fixes/relationships_rdoc_example_try.rb +83 -0
- data/try/bug_fixes/stale_unique_index_try.rb +124 -0
- data/try/edge_cases/fast_writer_transaction_guard_try.rb +2 -4
- data/try/features/atomic_write_coverage_try.rb +2 -4
- data/try/features/dirty_tracking_try.rb +2 -4
- data/try/features/dirty_write_new_object_try.rb +22 -1
- data/try/features/dirty_write_warnings_try.rb +2 -1
- data/try/features/encrypted_fields/aad_nil_fields_try.rb +4 -6
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -6
- data/try/features/encrypted_fields/aad_roundtrip_try.rb +4 -6
- data/try/features/encrypted_fields/aad_transient_fix_try.rb +4 -6
- data/try/features/encrypted_fields/aad_transient_proof_try.rb +4 -6
- data/try/features/encrypted_fields/concealed_string_core_try.rb +8 -6
- data/try/features/encrypted_fields/context_isolation_try.rb +2 -4
- data/try/features/encrypted_fields/encrypted_data_try.rb +2 -4
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +42 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +17 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +2 -4
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +6 -0
- data/try/features/encrypted_fields/envelope_version_branching_try.rb +4 -6
- data/try/features/encrypted_fields/envelope_version_try.rb +4 -6
- data/try/features/encrypted_fields/error_conditions_try.rb +2 -4
- data/try/features/encrypted_fields/fast_writer_try.rb +4 -6
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +2 -4
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -2
- data/try/features/encrypted_fields/key_material_try.rb +4 -6
- data/try/features/encrypted_fields/key_rotation_try.rb +6 -7
- data/try/features/encrypted_fields/memory_security_try.rb +5 -5
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +2 -4
- data/try/features/encrypted_fields/per_field_algorithm_try.rb +5 -2
- data/try/features/encrypted_fields/re_encrypt_fields_try.rb +11 -19
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +5 -5
- data/try/features/encrypted_fields/thread_safety_try.rb +2 -4
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +5 -5
- data/try/features/encryption/aes_gcm_salt_rotation_try.rb +24 -5
- data/try/features/encryption/algorithm_upgrade_try.rb +7 -7
- data/try/features/encryption/config_persistence_try.rb +42 -5
- data/try/features/encryption/core_try.rb +4 -1
- data/try/features/encryption/encoding_phase1_try.rb +4 -1
- data/try/features/encryption/encoding_phase2_try.rb +4 -1
- data/try/features/encryption/instance_variable_scope_try.rb +5 -4
- data/try/features/encryption/module_loading_try.rb +7 -5
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +51 -3
- data/try/features/encryption/request_cache_try.rb +4 -7
- data/try/features/encryption/roundtrip_validation_try.rb +3 -0
- data/try/features/encryption/secure_memory_handling_try.rb +76 -5
- data/try/features/encryption/xchacha20_personalization_rotation_try.rb +230 -0
- data/try/features/housekeeping/enforce_collection_caps_try.rb +136 -0
- data/try/features/housekeeping/housekeeping_try.rb +2 -2
- data/try/features/instance_registry_try.rb +6 -14
- data/try/features/real_feature_integration_try.rb +8 -0
- data/try/features/relationships/class_level_multi_index_try.rb +30 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +19 -0
- data/try/features/relationships/relationships_edge_cases_try.rb +3 -5
- data/try/features/relationships/score_encoding_permissions_try.rb +245 -0
- data/try/features/relationships/unique_index_cas_try.rb +546 -0
- data/try/features/transient_fields/refresh_reset_try.rb +4 -1
- data/try/features/transient_fields/single_use_redacted_string_try.rb +34 -0
- data/try/integration/connection/isolated_dbclient_try.rb +34 -22
- data/try/integration/connection/middleware_reconnect_try.rb +3 -3
- data/try/integration/connection/pools_try.rb +22 -13
- data/try/integration/persistence_operations_try.rb +2 -4
- data/try/integration/save_methods_consistency_try.rb +52 -6
- data/try/investigation/pipeline_routing/CONCLUSION.md +149 -0
- data/try/investigation/pipeline_routing/FINDINGS.md +168 -0
- data/try/{features/transient_fields → support/debugging}/simple_refresh_test.rb +2 -2
- data/try/support/encryption_config_helper_try.rb +114 -0
- data/try/support/helpers/encryption_config.rb +169 -0
- data/try/support/helpers/test_helpers.rb +47 -0
- data/try/support/prototypes/pooling/docs/README_advanced_usage.md +636 -0
- data/try/support/prototypes/pooling/docs/README_stress_testing.md +200 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +2 -4
- data/try/unit/core/suite_hygiene_try.rb +32 -0
- data/try/unit/core/tools_try.rb +4 -2
- data/try/unit/data_types/enumerable_consistency/large_scale_consistency_try.rb +14 -3
- data/try/unit/data_types/lock_try.rb +44 -3
- data/try/unit/data_types/max_length_try.rb +607 -0
- data/try/unit/horreum/automatic_index_validation_try.rb +89 -2
- data/try/unit/horreum/commands_try.rb +85 -0
- data/try/unit/horreum/destroy_index_cleanup_try.rb +714 -22
- data/try/unit/horreum/multi_field_update_try.rb +154 -1
- data/try/unit/horreum/serialization_try.rb +2 -2
- data/try/unit/horreum/unique_index_edge_cases_try.rb +46 -7
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
- data/try/unit/middleware/database_logger_methods_try.rb +39 -0
- data/try/unit/multi_result_try.rb +298 -0
- data/try/unit/thread_safety_monitor_try.rb +20 -12
- metadata +30 -36
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d860c29966392e82cf54d842d457353d6a7268c3bf5378e25af9cb850f59d37c
|
|
4
|
+
data.tar.gz: 87fc36560a6cd68ffb614ef6b3ab11625300ebed8433508739f1ab4633f5b9ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15b5c9028503566db28d179052982d3a38617463753ea360f22ea49eeb86d3fc896352f18c73a703fe62d3fd5a6cdcf65f254dab284a7671f3b9a0b9db413acb
|
|
7
|
+
data.tar.gz: 633e8ae2bab11aaf362791f23f68ea7f0287e1388c38850200809c5b43ed79ae59039492258927023ea91821d39243a18c9eaa5a63d8c068fb011edc99120468
|
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
|
|
49
49
|
- name: Run Claude Code Review
|
|
50
50
|
id: claude-review
|
|
51
|
-
uses: anthropics/claude-code-action@
|
|
51
|
+
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1.0.183
|
|
52
52
|
with:
|
|
53
53
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
54
54
|
|
|
@@ -56,13 +56,23 @@ jobs:
|
|
|
56
56
|
# runs) -> the CLAUDE_MODEL repo variable -> this built-in default.
|
|
57
57
|
# Pinning a current id avoids the action's frozen default, which 404s
|
|
58
58
|
# ("model: claude-sonnet-4-20250514"). Fall back to Sonnet on overload.
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
# v1 of the action dropped the model/fallback_model/allowed_tools
|
|
60
|
+
# inputs; they are CLI flags passed via claude_args now (see
|
|
61
|
+
# docs/migration-guide.md and docs/usage.md in the action repo).
|
|
62
|
+
claude_args: |
|
|
63
|
+
--model ${{ inputs.model || vars.CLAUDE_MODEL || 'claude-opus-4-6' }}
|
|
64
|
+
--fallback-model ${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}
|
|
65
|
+
--allowedTools "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:*)"
|
|
61
66
|
|
|
62
67
|
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
|
63
68
|
use_sticky_comment: true
|
|
64
69
|
|
|
65
|
-
direct_prompt
|
|
70
|
+
# v1 renamed direct_prompt -> prompt. The REPO/PR NUMBER header is the
|
|
71
|
+
# format the migration guide calls for so Claude reviews the right PR.
|
|
72
|
+
prompt: |
|
|
73
|
+
REPO: ${{ github.repository }}
|
|
74
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
75
|
+
|
|
66
76
|
Please review this pull request and provide feedback on:
|
|
67
77
|
- Code quality and best practices
|
|
68
78
|
- Potential bugs or issues
|
|
@@ -73,7 +83,3 @@ jobs:
|
|
|
73
83
|
Use the repository's AGENTS.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
|
74
84
|
|
|
75
85
|
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
|
76
|
-
|
|
77
|
-
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
78
|
-
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
|
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:*)"
|
|
@@ -32,47 +32,31 @@ jobs:
|
|
|
32
32
|
|
|
33
33
|
- name: Run Claude Code
|
|
34
34
|
id: claude
|
|
35
|
-
uses: anthropics/claude-code-action@
|
|
35
|
+
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1.0.183
|
|
36
36
|
with:
|
|
37
37
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
38
|
|
|
39
39
|
# Primary model: set the CLAUDE_MODEL repo variable to override without
|
|
40
40
|
# editing this file. Pinning a current id avoids the action's frozen
|
|
41
41
|
# default, which 404s ("model: claude-sonnet-4-20250514"). Fall back to
|
|
42
|
-
# Sonnet if the primary is unavailable or overloaded.
|
|
43
|
-
model
|
|
44
|
-
|
|
42
|
+
# Sonnet if the primary is unavailable or overloaded. v1 of the action
|
|
43
|
+
# dropped the model/fallback_model inputs; models are CLI flags passed
|
|
44
|
+
# via claude_args (see docs/migration-guide.md in the action repo).
|
|
45
|
+
# Other supported flags (--allowedTools, --max-turns, ...):
|
|
46
|
+
# https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
47
|
+
claude_args: |
|
|
48
|
+
--model ${{ vars.CLAUDE_MODEL || 'claude-opus-4-6' }}
|
|
49
|
+
--fallback-model ${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}
|
|
45
50
|
|
|
46
51
|
# This is an optional setting that allows Claude to read CI results on PRs
|
|
47
52
|
additional_permissions: |
|
|
48
53
|
actions: read
|
|
49
54
|
|
|
50
|
-
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
51
|
-
# model: "claude-opus-4-20250514"
|
|
52
|
-
|
|
53
55
|
# Optional: Customize the trigger phrase (default: @claude)
|
|
54
56
|
# trigger_phrase: "/claude"
|
|
55
57
|
|
|
56
58
|
# Optional: Trigger when specific user is assigned to an issue
|
|
57
59
|
# assignee_trigger: "claude-bot"
|
|
58
60
|
|
|
59
|
-
# Optional: Allow Claude to run specific commands
|
|
60
|
-
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
|
|
61
|
-
|
|
62
|
-
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
|
63
|
-
# custom_instructions: |
|
|
64
|
-
# Follow our coding standards
|
|
65
|
-
# Ensure all new code has tests
|
|
66
|
-
# Use TypeScript for new files
|
|
67
|
-
|
|
68
|
-
# Optional: Custom environment variables for Claude
|
|
69
|
-
# claude_env: |
|
|
70
|
-
# NODE_ENV: test
|
|
71
|
-
|
|
72
61
|
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
|
73
62
|
# prompt: 'Update the pull request description to include a summary of changes.'
|
|
74
|
-
|
|
75
|
-
# Optional: Add claude_args to customize behavior and configuration
|
|
76
|
-
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
77
|
-
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
|
78
|
-
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
|
data/.gitignore
CHANGED
|
@@ -24,8 +24,14 @@ vendor
|
|
|
24
24
|
*.gem
|
|
25
25
|
public/
|
|
26
26
|
|
|
27
|
-
# Ignore WIP or temp dev files with uppercase names
|
|
28
|
-
|
|
27
|
+
# Ignore WIP or temp dev files with uppercase names, at the repo root only.
|
|
28
|
+
# Anchored because core.ignoreCase is true on macOS/Windows checkouts: git
|
|
29
|
+
# casefolds the [A-Z] class, so an unanchored pattern silently swallowed every
|
|
30
|
+
# new lowercase .md anywhere in the tree (docs/*.md included).
|
|
31
|
+
/[A-Z]*.md
|
|
32
|
+
|
|
33
|
+
# Unredacted security notes stay local; publish redacted summaries only
|
|
34
|
+
*.private.md
|
|
29
35
|
|
|
30
36
|
# Exclusions
|
|
31
37
|
!README.md
|
data/.talismanrc
CHANGED
|
@@ -12,4 +12,17 @@ fileignoreconfig:
|
|
|
12
12
|
checksum: 10c94f802b2fa3a39fa33bf7dc34dac2ecaa2dd453ff97a19313f96a32fadeff
|
|
13
13
|
- filename: examples/encrypted_fields.rb
|
|
14
14
|
checksum: d1bd77f85d951d367e1c2bfd066a1d68d5f486346ee479121a3d5dcc2560bf52
|
|
15
|
+
# SHA-pinned actions read as "hex encoded text" to talisman; the pin is the point.
|
|
16
|
+
- filename: .github/workflows/claude.yml
|
|
17
|
+
checksum: 3a7acf45fb032644c40bad629ba6c0271689a1233ef6893aba3caf03f6327b50
|
|
18
|
+
- filename: .github/workflows/claude-code-review.yml
|
|
19
|
+
checksum: 698ebf39c1c49a72212cd8d9fd0117aaf15079dcb9f1e22c94dc7a871dd7952e
|
|
20
|
+
- filename: docs/adr/0002-watch-for-private-keys-lua-for-shared-keys.md
|
|
21
|
+
checksum: 0ace51636017e6d0a5b489dc14fc6b684cabd64c6bb706a710f869d31ea5f4f0
|
|
22
|
+
- filename: .github/workflows/ruby-lint.yml
|
|
23
|
+
checksum: b8009bc29189d214229c88f1cf7960c651404e659be74436a79efb130177ff58
|
|
24
|
+
- filename: .github/workflows/ci.yml
|
|
25
|
+
checksum: 0b356104ddf0b192d29b25669c61b17c863eca5de0e36822ea4d7b353d065016
|
|
26
|
+
- filename: .github/workflows/release-gem.yml
|
|
27
|
+
checksum: 1ad4786c939da966b79275105f60005c7a3214f424d5701d8118a7175b087112
|
|
15
28
|
version: ""
|
data/AGENTS.md
CHANGED
|
@@ -21,6 +21,13 @@ Run with `--agent` for token-efficient output (`--agent-focus summary|first-fail
|
|
|
21
21
|
See `bundle exec try --help` for the full CLI, framework integration (`--rspec`,
|
|
22
22
|
`--minitest`), and debugging flags.
|
|
23
23
|
|
|
24
|
+
The whole suite runs in one process, so anything a file sets globally outlives
|
|
25
|
+
it. For encryption keys use the scoped helpers rather than assigning
|
|
26
|
+
`Familia.config.encryption_keys` directly: `set_test_encryption_keys(keys,
|
|
27
|
+
current_version:)` in setup with `clear_test_encryption_keys` in teardown, or
|
|
28
|
+
`with_test_encryption_keys(keys, current_version:) { ... }` for a single
|
|
29
|
+
testcase. See @try/support/helpers/encryption_config.rb.
|
|
30
|
+
|
|
24
31
|
### Changelog
|
|
25
32
|
|
|
26
33
|
Add a changelog fragment (RST) with each user-facing change. See @changelog.d/README.md
|
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,97 @@ 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.12.0:
|
|
11
|
+
|
|
12
|
+
2.12.0 — 2026-08-03
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Added
|
|
16
|
+
-----
|
|
17
|
+
|
|
18
|
+
- Added ``Familia::HashKey#claim_field`` and ``#release_field`` for server-side compare-and-set/compare-and-delete on single hash fields. Raises ``Familia::OperationModeError`` in pipelines/transactions.
|
|
19
|
+
- Added generated ``claim_unique_<index>!`` and ``release_unique_<index>!`` instance methods with automatic partial claim rollback on unique index save collisions. #353
|
|
20
|
+
- Added tryout test helpers (``set_test_encryption_keys``, ``clear_test_encryption_keys``, ``with_test_encryption_keys``) to safely manage encryption key configurations during test runs. #363
|
|
21
|
+
- Added ``Familia::MultiResult#aborted?`` to distinguish WATCH aborts from individual command errors.
|
|
22
|
+
- Added ``Familia::MultiResult#inspect`` for log-safe, compact transaction outcome summaries.
|
|
23
|
+
- Added ``max_length:`` option to ``SortedSet`` and ``ListKey`` to automatically cap collection sizes at write time (retaining newest N elements). #351
|
|
24
|
+
- Added ``DataType#max_length`` to query configured collection caps, with definition-time validation of the parameter. #351
|
|
25
|
+
- Added ``SortedSet#enforce_max_length!`` and ``ListKey#enforce_max_length!`` to trim existing collections. #351
|
|
26
|
+
- Added ``max_length:`` option to ``participates_in`` and ``class_participates_in``. #351
|
|
27
|
+
- Added ``Familia::Features::Housekeeping::EnforceCollectionCaps`` chore class to support bulk cap enforcement. #351
|
|
28
|
+
- Added ``encryption_personalization_history`` setting to support key personalization rotation for XChaCha20-Poly1305 providers. #333
|
|
29
|
+
- Added ``limit:``, ``offset:``, and ``each_<collection>_with_permission`` to stream permission-filtered collection members via ``ZSCAN`` with O(1) memory. #309
|
|
30
|
+
|
|
31
|
+
Changed
|
|
32
|
+
-------
|
|
33
|
+
|
|
34
|
+
- Unique index updates (``add_to_class_<index>``, ``update_in_class_<index>``) now raise ``Familia::OperationModeError`` inside transactions unless the written value was previously claimed. #353
|
|
35
|
+
- Retained ``guard_unique_indexes!`` for fast-fail read checks before acquiring index claims. #353
|
|
36
|
+
- Calls to ``remove_from_class_*`` and class-level ``destroy!`` no longer evict index entries that point to different identifiers. #353
|
|
37
|
+
- ``Familia::MultiResult#results`` now always returns an ``Array`` (empty instead of ``nil`` on transaction abort) to prevent upstream crashes.
|
|
38
|
+
- ``Familia::MultiResult#to_h`` now includes an ``:aborted`` boolean key.
|
|
39
|
+
- ``Familia::MultiResult`` instances are now read-only, freezing both ``#results`` and ``#errors``.
|
|
40
|
+
- ``SortedSet#increment`` now runs the ``warn_if_dirty!`` guard, warning or raising when called on an unsaved parent. #351
|
|
41
|
+
- ``Manager#decrypt`` now handles XChaCha20 provider personalization candidates correctly during decryption walks, matching AES-GCM salt history behavior. #333
|
|
42
|
+
- The request-scoped key cache key now includes a personalization segment to prevent collisions across different rotation candidates. #333
|
|
43
|
+
- ``multi_field_fast_write`` now raises ``Familia::IndexedFieldFastWriteError`` if any written field backs a class-level index. #308
|
|
44
|
+
- Fast writer ``field!`` on a class-indexed field now raises ``Familia::IndexedFieldFastWriteError`` inside transactions/pipelines, and raises ``Familia::PersistenceError`` on unsaved records. #308
|
|
45
|
+
- With a blank ``encryption_hkdf_salt``, ``Manager#encrypt`` now raises ``EncryptionError`` when the request-cache key is built rather than at key derivation. The raise happens earlier and now also fires on what would previously have been a warm-cache hit; correctly configured deployments see no change. #380
|
|
46
|
+
|
|
47
|
+
Removed
|
|
48
|
+
-------
|
|
49
|
+
|
|
50
|
+
- Removed the unused ``ScoreEncoding.category_score_range`` method.
|
|
51
|
+
- Removed unused ``csv`` and ``stringio`` runtime dependencies from the gemspec. #354
|
|
52
|
+
- Removed dead private helper methods ``Horreum::define_attr_accessor_methods``, ``remove_stale_collection_member``, and ``define_fast_writer_method``. #347, #308
|
|
53
|
+
|
|
54
|
+
Fixed
|
|
55
|
+
-----
|
|
56
|
+
|
|
57
|
+
- Fixed ``decrby`` and ``decr`` (and aliases) to correctly use ``HINCRBY`` with a negated amount and added client-side integer validation.
|
|
58
|
+
- Fixed ``encryption_info`` to reference the correct provider APIs and accurately expose ``key_size``.
|
|
59
|
+
- Fixed a TOCTOU race in ``unique_index`` by enforcing server-side CAS claims using Lua before opening transactions, raising ``Familia::RecordExistsError`` on collision. #353
|
|
60
|
+
- Fixed ``remove_from_class_*`` and ``update_in_class_*`` to use ownership-checked deletes, preventing accidental deletion of other records' valid entries. #353
|
|
61
|
+
- Fixed process-global encryption configuration leaks across tryouts by using scoped helpers and restoring baselines. #363
|
|
62
|
+
- Fixed test isolation in ``real_feature_integration_try.rb`` and ``module_loading_try.rb`` by explicitly managing encryption key rings. #363
|
|
63
|
+
- Fixed fiber-local key cache teardowns to clear the correct request cache key. #363
|
|
64
|
+
- Fixed claim leaks on unpersisted records in ``update_in_<scope>_<index_name>`` by running the persisted-record guard before checking claims. #370
|
|
65
|
+
- Fixed ``ConcealedString`` by removing a redundant and non-functional GC finalizer. #359
|
|
66
|
+
- Fixed ``Familia::MultiResult`` to prevent ``NoMethodError`` crashes on WATCH-aborted transactions. #355
|
|
67
|
+
- Fixed ``SecureXChaCha20Poly1305Provider#derive_key`` to avoid mutating the receiver context string and handle non-String contexts correctly. #356
|
|
68
|
+
- Fixed ``SortedSet#increment`` inside transactions/pipelines to safely return the future instead of calling ``to_f`` prematurely. #351
|
|
69
|
+
- Fixed long-ignored ``:maxlength`` spelling to explicitly warn at definition time, prompting rename to ``max_length:``. #351
|
|
70
|
+
- Fixed ``Lock#acquire`` with a positive TTL to run as a single atomic ``SET NX EX`` command. #347
|
|
71
|
+
- Fixed ``DatabaseLogger.sample_rate`` assignment to validate and clamp input values, preventing crashes on the hot path. #347
|
|
72
|
+
- Fixed ``SingleUseRedactedString`` to be loaded automatically by ``require 'familia'``. #347
|
|
73
|
+
- Fixed memory overhead in ``<collection>_with_permission`` queries by batching and paging internally via ``ZRANGEBYSCORE ... LIMIT``. #309
|
|
74
|
+
- Fixed performance by removing redundant post-save ``update_all_indexes`` calls inside relationships save. #307
|
|
75
|
+
- Fixed partial write paths (``commit_fields``, ``save_fields``, ``multi_field_update``) to safely guard and claim unique indexes before executing writes. #308
|
|
76
|
+
- Fixed fast writers (``field!``) on indexed fields to claim and update the index atomically before execution. #308
|
|
77
|
+
|
|
78
|
+
Security
|
|
79
|
+
--------
|
|
80
|
+
|
|
81
|
+
- Enforced field-type semantics in ``multi_field_update`` and ``multi_field_fast_write`` to prevent writing plaintext to encrypted fields or persisting transient fields.
|
|
82
|
+
- Enforced loud failures for invalid permission symbol lookups in ``Relationships::ScoreEncoding`` to prevent silent misconfigurations.
|
|
83
|
+
- Pinned GitHub Workflows holding ``CLAUDE_CODE_OAUTH_TOKEN`` to immutable release commit SHAs.
|
|
84
|
+
- The encrypt-path request-cache key now resolves the HKDF salt through the fail-closed ``current_hkdf_salt`` accessor instead of the permissive candidate list's head (``hkdf_salts.first``). Previously, with a blank ``encryption_hkdf_salt``, a decrypt inside ``with_request_cache`` could warm an entry keyed on a historical salt that a subsequent encrypt would silently reuse -- because the cache lookup precedes derivation, the blank-salt refusal in the provider never fired. Encrypts now refuse a blank salt even against a warm cache. #380
|
|
85
|
+
|
|
86
|
+
Documentation
|
|
87
|
+
-------------
|
|
88
|
+
|
|
89
|
+
- Replaced fabricated/incorrect examples in ``docs/overview.md`` and RDoc comments with accurate, tested examples for permission management and relationships.
|
|
90
|
+
- Documented that ``<collection>_with_permission`` requires atomic flags and rejects role symbols.
|
|
91
|
+
- Corrected the error message for ``guard_allowed_fields!``.
|
|
92
|
+
- Documented ``PERMISSION_CATEGORIES`` and exclusive permission tiers.
|
|
93
|
+
- Fixed incorrect connection provider examples in docs.
|
|
94
|
+
- Updated encryption guides to cover personalization history and rotation.
|
|
95
|
+
|
|
96
|
+
AI Assistance
|
|
97
|
+
-------------
|
|
98
|
+
|
|
99
|
+
- Core fixes, tryout coverage (120+ test cases), and documentation updates implemented with AI assistance.
|
|
100
|
+
|
|
10
101
|
.. _changelog-2.11.2:
|
|
11
102
|
|
|
12
103
|
2.11.2 — 2026-07-05
|
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.
|
|
8
|
+
gem 'concurrent-ruby', '~> 1.3.8', require: false
|
|
9
9
|
gem 'ruby-prof'
|
|
10
10
|
gem 'stackprof'
|
|
11
11
|
gem 'timecop', require: false
|
|
@@ -26,7 +26,7 @@ group :development, :test do
|
|
|
26
26
|
gem 'rake', '~> 13.0', require: false
|
|
27
27
|
gem 'redcarpet', require: false
|
|
28
28
|
gem 'reek', require: false
|
|
29
|
-
gem 'rubocop', '~> 1.88.
|
|
29
|
+
gem 'rubocop', '~> 1.88.2', require: false
|
|
30
30
|
gem 'rubocop-performance', require: false
|
|
31
31
|
gem 'rubocop-thread_safety', require: false
|
|
32
32
|
gem 'ruby-lsp', require: false
|
data/Gemfile.lock
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
familia (2.
|
|
4
|
+
familia (2.12.0)
|
|
5
5
|
concurrent-ruby (~> 1.3)
|
|
6
6
|
connection_pool (>= 2.4, < 4.0)
|
|
7
|
-
csv (~> 3.3)
|
|
8
7
|
json_schemer (~> 2.0)
|
|
9
8
|
logger (~> 1.7)
|
|
10
9
|
oj (~> 3.16)
|
|
11
10
|
redis (>= 5.0, < 6.0)
|
|
12
|
-
stringio (>= 3.1.1, < 3.3.0)
|
|
13
11
|
uri-valkey (~> 1.4)
|
|
14
12
|
|
|
15
13
|
GEM
|
|
@@ -19,9 +17,8 @@ GEM
|
|
|
19
17
|
base64 (0.3.0)
|
|
20
18
|
benchmark (0.5.0)
|
|
21
19
|
bigdecimal (4.1.2)
|
|
22
|
-
concurrent-ruby (1.3.
|
|
20
|
+
concurrent-ruby (1.3.8)
|
|
23
21
|
connection_pool (3.0.2)
|
|
24
|
-
csv (3.3.5)
|
|
25
22
|
date (3.5.1)
|
|
26
23
|
debug (1.11.1)
|
|
27
24
|
irb (~> 1.10)
|
|
@@ -66,17 +63,17 @@ GEM
|
|
|
66
63
|
prism (>= 1.3.0)
|
|
67
64
|
rdoc (>= 4.0.0)
|
|
68
65
|
reline (>= 0.4.2)
|
|
69
|
-
json (2.
|
|
66
|
+
json (2.20.0)
|
|
70
67
|
json_schemer (2.5.0)
|
|
71
68
|
bigdecimal
|
|
72
69
|
hana (~> 1.3)
|
|
73
70
|
regexp_parser (~> 2.0)
|
|
74
71
|
simpleidn (~> 0.2)
|
|
75
|
-
language_server-protocol (3.17.0.
|
|
72
|
+
language_server-protocol (3.17.0.6)
|
|
76
73
|
lint_roller (1.1.0)
|
|
77
74
|
logger (1.7.0)
|
|
78
75
|
minitest (5.27.0)
|
|
79
|
-
oj (3.17.
|
|
76
|
+
oj (3.17.4)
|
|
80
77
|
bigdecimal (>= 3.0)
|
|
81
78
|
ostruct (>= 0.2)
|
|
82
79
|
ostruct (0.6.3)
|
|
@@ -98,7 +95,7 @@ GEM
|
|
|
98
95
|
rake (13.4.2)
|
|
99
96
|
rbnacl (7.1.2)
|
|
100
97
|
ffi (~> 1)
|
|
101
|
-
rbs (4.0.
|
|
98
|
+
rbs (4.0.3)
|
|
102
99
|
logger
|
|
103
100
|
prism (>= 1.6.0)
|
|
104
101
|
tsort
|
|
@@ -134,7 +131,7 @@ GEM
|
|
|
134
131
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
135
132
|
rspec-support (~> 3.13.0)
|
|
136
133
|
rspec-support (3.13.7)
|
|
137
|
-
rubocop (1.88.
|
|
134
|
+
rubocop (1.88.2)
|
|
138
135
|
json (~> 2.3)
|
|
139
136
|
language_server-protocol (~> 3.17.0.2)
|
|
140
137
|
lint_roller (~> 1.1.0)
|
|
@@ -145,7 +142,7 @@ GEM
|
|
|
145
142
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
146
143
|
ruby-progressbar (~> 1.7)
|
|
147
144
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
148
|
-
rubocop-ast (1.
|
|
145
|
+
rubocop-ast (1.50.0)
|
|
149
146
|
parser (>= 3.3.7.2)
|
|
150
147
|
prism (~> 1.7)
|
|
151
148
|
rubocop-performance (1.26.1)
|
|
@@ -156,7 +153,7 @@ GEM
|
|
|
156
153
|
lint_roller (~> 1.1)
|
|
157
154
|
rubocop (~> 1.72, >= 1.72.1)
|
|
158
155
|
rubocop-ast (>= 1.44.0, < 2.0)
|
|
159
|
-
ruby-lsp (0.26.
|
|
156
|
+
ruby-lsp (0.26.10)
|
|
160
157
|
language_server-protocol (~> 3.17.0)
|
|
161
158
|
prism (>= 1.2, < 2.0)
|
|
162
159
|
rbs (>= 3, < 5)
|
|
@@ -185,7 +182,7 @@ GEM
|
|
|
185
182
|
unicode-emoji (~> 4.1)
|
|
186
183
|
unicode-emoji (4.2.0)
|
|
187
184
|
uri-valkey (1.4.0)
|
|
188
|
-
yard (0.9.
|
|
185
|
+
yard (0.9.45)
|
|
189
186
|
zeitwerk (2.8.2)
|
|
190
187
|
|
|
191
188
|
PLATFORMS
|
|
@@ -194,7 +191,7 @@ PLATFORMS
|
|
|
194
191
|
|
|
195
192
|
DEPENDENCIES
|
|
196
193
|
benchmark (~> 0.4)
|
|
197
|
-
concurrent-ruby (~> 1.3.
|
|
194
|
+
concurrent-ruby (~> 1.3.8)
|
|
198
195
|
debug
|
|
199
196
|
dry-configurable (>= 1.3, < 1.5)
|
|
200
197
|
familia!
|
|
@@ -204,7 +201,7 @@ DEPENDENCIES
|
|
|
204
201
|
rbnacl (~> 7.1, >= 7.1.1)
|
|
205
202
|
redcarpet
|
|
206
203
|
reek
|
|
207
|
-
rubocop (~> 1.88.
|
|
204
|
+
rubocop (~> 1.88.2)
|
|
208
205
|
rubocop-performance
|
|
209
206
|
rubocop-thread_safety
|
|
210
207
|
ruby-lsp
|
data/README.md
CHANGED
|
@@ -377,13 +377,24 @@ end
|
|
|
377
377
|
```ruby
|
|
378
378
|
require 'connection_pool'
|
|
379
379
|
|
|
380
|
+
POOLS = {}
|
|
381
|
+
POOLS_MUTEX = Mutex.new
|
|
382
|
+
|
|
380
383
|
Familia.connection_provider = lambda do |uri|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
+
POOLS_MUTEX.synchronize do
|
|
385
|
+
POOLS[uri] ||= ConnectionPool::Wrapper.new(size: 10, timeout: 5) do
|
|
386
|
+
Redis.new(url: uri)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
384
389
|
end
|
|
385
390
|
```
|
|
386
391
|
|
|
392
|
+
Build each pool once, outside the lambda, and return a `ConnectionPool::Wrapper`
|
|
393
|
+
— it checks a connection out for the duration of each command and checks it back
|
|
394
|
+
in afterwards. Returning `pool.with { |conn| conn }` instead hands back a
|
|
395
|
+
connection the pool already considers free, so concurrent callers share it. See
|
|
396
|
+
[the provider contract](docs/reference/api-technical.md#provider-contract).
|
|
397
|
+
|
|
387
398
|
### Encryption Setup
|
|
388
399
|
|
|
389
400
|
```ruby
|
data/changelog.d/README.md
CHANGED
|
@@ -4,64 +4,56 @@ This directory contains changelog fragments managed by [Scriv](https://scriv.rea
|
|
|
4
4
|
|
|
5
5
|
## Our Approach
|
|
6
6
|
|
|
7
|
-
Changelogs are for humans and agents, not just machines. We follow
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* `
|
|
21
|
-
* `
|
|
22
|
-
* `
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Include technical details to help developers update to the new version. Start with a specific introduction, e.g. "This version introduces significant improvements to Familia's feature system, making it easier to organize and use features across complex projects.". Including code snippets and multi-line content that is too detailed for the CHANGELOG.
|
|
40
|
-
|
|
41
|
-
Use the content of an existing `docs/migrating/vMajor.Minor.Patch*.md file as a reference.
|
|
42
|
-
|
|
43
|
-
Compare the headers of your draft content with the headers of the previous migration guide to make sure it does not repeat or overlap.
|
|
44
|
-
|
|
45
|
-
4. **Commit with Your Code:**
|
|
46
|
-
```bash
|
|
47
|
-
git add changelog.d/YYYYMMDD_HHmmss_username_branch.rst [docs/migrating/v2.0.0-pre.md]
|
|
48
|
-
git commit
|
|
49
|
-
```
|
|
7
|
+
Changelogs are for humans and agents, not just machines. We follow [Keep a Changelog](https://keepachangelog.com) and semver to ensure clear, consistent, and useful release notes.
|
|
8
|
+
|
|
9
|
+
We use a fragment-based workflow with `scriv`. Each developer includes a small, focused changelog fragment with their pull request. At release time, these are compiled into the main changelog.
|
|
10
|
+
|
|
11
|
+
Benefits:
|
|
12
|
+
- **No Merge Conflicts:** Developers work in parallel without conflicting over a single file.
|
|
13
|
+
- **Improved DX:** Creating a small fragment is simple and repeatable.
|
|
14
|
+
- **AI Transparency:** Briefly notes AI involvement without cluttering technical sections.
|
|
15
|
+
- **Consistency:** Automation maintains a unified structure.
|
|
16
|
+
|
|
17
|
+
### Relevant Paths
|
|
18
|
+
|
|
19
|
+
* `changelog.d/` - Fragment directory (e.g. `changelog.d/YYYYMMDD_HHmmss_username_branch.rst`)
|
|
20
|
+
* `docs/migrating/` - Migration guides (e.g. `docs/migrating/v2.0.0-pre.md`)
|
|
21
|
+
* `CHANGELOG.rst` - The full changelog (reverse chronological, large file; read only the top 100 lines default)
|
|
22
|
+
* `changelog.d/scriv.ini` - Scriv configuration
|
|
23
|
+
|
|
24
|
+
## Add a Changelog Entry
|
|
25
|
+
|
|
26
|
+
1. **Create a New Fragment:**
|
|
27
|
+
```bash
|
|
28
|
+
scriv create
|
|
29
|
+
```
|
|
30
|
+
2. **Edit the Fragment:**
|
|
31
|
+
Open the new `.rst` file and write your entry under the relevant category using the guidelines below.
|
|
32
|
+
3. **Add or Update Migrating Guide (Optional):**
|
|
33
|
+
If the change requires developer action to upgrade, add or update a guide in `docs/migrating/`. Use existing guides as a reference and ensure headers do not repeat.
|
|
34
|
+
4. **Commit with Your Code:**
|
|
35
|
+
```bash
|
|
36
|
+
git add changelog.d/YYYYMMDD_HHmmss_username_branch.rst [docs/migrating/v2.0.0-pre.md]
|
|
37
|
+
git commit
|
|
38
|
+
```
|
|
50
39
|
|
|
51
40
|
## Fragment Guidelines
|
|
52
41
|
|
|
53
42
|
- **One Fragment Per Change:** Keep each fragment focused on a single feature, fix, or improvement.
|
|
54
|
-
- **
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- **
|
|
59
|
-
- **
|
|
60
|
-
|
|
43
|
+
- **Reference Context:** Include issue or PR numbers. Scriv will automatically link them. (e.g., `PR #123`).
|
|
44
|
+
|
|
45
|
+
## Content Guidelines
|
|
46
|
+
|
|
47
|
+
- **Target the Consumer:** Focus exclusively on external, breaking, or actionable behavior. Omit internal implementation steps, development metadata, and agent logs.
|
|
48
|
+
- **Impact-Driven Filtering:** Focus strictly on technical facts (method/class signatures, parameters, exceptions, issues resolved) and eliminate explanatory "how" or "why" narratives.
|
|
49
|
+
- **Good:** "Added ``Familia::HashKey#claim_field`` and ``#release_field`` for single-hash server-side CAS/CAD operations. Raises ``Familia::OperationModeError`` in pipelines/transactions."
|
|
50
|
+
- **Bad:** "We found a race condition during an audit, so we added a nice compare-and-set wrapper called `#claim_field` to allow callers to safely claim a field in single hash fields."
|
|
51
|
+
- **Process Log Exclusion:** Remove all development metadata (such as tool-specific implementation notes, agent logs, and audit timelines) that do not change library APIs or behavior.
|
|
52
|
+
- **Maintain Consistency:** Match the terse style, semantic classification, and spacing of previous changelog versions.
|
|
61
53
|
|
|
62
|
-
|
|
54
|
+
## Categories
|
|
63
55
|
|
|
64
|
-
Use these
|
|
56
|
+
Use these standard headers in your fragment:
|
|
65
57
|
|
|
66
58
|
- **Added**: New features or capabilities.
|
|
67
59
|
- **Changed**: Changes to existing functionality.
|
|
@@ -70,8 +62,8 @@ Use these categories:
|
|
|
70
62
|
- **Fixed**: Bug fixes.
|
|
71
63
|
- **Security**: Security-related improvements.
|
|
72
64
|
- **Documentation**: Documentation improvements.
|
|
73
|
-
- **AI Assistance**:
|
|
65
|
+
- **AI Assistance**: Terse, single-sentence acknowledgment of AI assistance for the change. Do not duplicate technical details already listed in other categories.
|
|
74
66
|
|
|
75
67
|
## Release Process
|
|
76
68
|
|
|
77
|
-
At release
|
|
69
|
+
At release, run `scriv collect` to aggregate all fragments into `CHANGELOG.rst`. The version is parsed automatically from `lib/familia/version.rb`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
### Changed
|
|
2
|
+
|
|
3
|
+
- **Index mutations now require a persisted record**: the `add_to_*`/`update_in_*` methods generated by `unique_index`/`multi_index` — both instance-scoped (`within:`) and class-level (`add_to_class_*`/`update_in_class_*`) — raise `Familia::PersistenceError` when called directly on an object that has never been saved. Previously the entry landed in the index pointing at a record that did not exist yet — and if the process never saved, nothing (including `destroy!` cleanup) could find it. The check runs before any write, so a rejected call leaves no partial state, and is skipped inside transactions/pipelines — which is why the save path (auto-indexing runs inside the save MULTI) and index rebuilds are unaffected. Call `save` before indexing. (#282; surfaced by the #278 unsaved-parent write guard)
|
|
4
|
+
|
|
5
|
+
### AI Assistance
|
|
6
|
+
|
|
7
|
+
- Root-cause analysis of the #278/#282 guard interaction, the fail-fast persisted-record guard, and test updates were developed with AI assistance.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
### Added
|
|
2
|
+
|
|
3
|
+
- **Instance-scoped indexes now refresh on save**: once a membership has been registered with `add_to_*`, saving the record keeps its index entry current. Unique indexes route through `update_in_*` with the previous value from dirty tracking, so a changed indexed field retracts its stale entry and the freed value becomes reusable; multi indexes are add-only, matching the existing class-level decision that a value change does not retract prior buckets (`destroy!` still cleans every bucket — see the tracker cardinality note below — so add-only costs no cleanup coverage; call `update_in_*` to retract at change time instead). The refresh runs inside save's MULTI (alongside class-level index maintenance) using a tracker snapshot read just before the transaction opens, so index mutations commit atomically with the object hash. The *initial* `add_to_*` remains manual — save has no scope instance to infer. Note that `atomic_write` does **not** refresh instance-scoped indexes: it calls the shared persistence path from inside a MULTI it already opened, leaving no pre-transaction point at which the tracker snapshot could be read, so index entries silently keep their previous values on that path. Use `save`, or call `update_in_*` explicitly afterwards. (#282)
|
|
4
|
+
- **Instance-scoped unique indexes are validated on save**: changing an indexed field to a value another record already holds in that scope now raises `Familia::RecordExistsError` and leaves the index untouched, matching how class-level `unique_index` has always behaved on save. Previously the uniqueness guard existed only in `add_to_*`, never `update_in_*`, so the save-refresh path could silently evict the other record's entry — and because the evicted record's tracker still claimed the slot, its later `destroy!` would unindex the live record that had taken it. The check runs before the transaction opens, alongside the existing class-level `guard_unique_indexes!`, and only inspects fields that actually changed. (#282)
|
|
5
|
+
- **`dirty_write_warnings:` option on DataType**: overrides the parent class's dirty-write diagnostic mode for a single collection. Added for ORM-internal structures that the persistence path writes deliberately while the parent is dirty — the index scope tracker sets `:off`, since the warning is false there (the scalar write is queued in the same transaction) and `Familia.strict_write_order` would otherwise abort every save that refreshes an index.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Instance-scoped index cleanup now survives a changed field value**: the reverse index tracker (`_idx_scopes`) is a hash mapping each membership to the field value that was written into the index, instead of a set of memberships alone. Tracker cardinality mirrors index cardinality: unique entries are keyed by `scope_config`/`index_name`/`scope_id` (1:1 within a scope, so HSET-overwrite is exact), while multi entries append the value, giving one entry per bucket the object occupies — a single triple-keyed entry could only name one of several simultaneous buckets, and `destroy!` would orphan the rest. `destroy!` removes the recorded buckets rather than re-reading the current field value, so cleanup still lands correctly when the indexed field changed after `add_to_*` — and when the object being destroyed was constructed identifier-only (`Model.new(id: x).destroy!`), where the current value is nil and cleanup previously short-circuited and orphaned the entry. `update_in_*` now updates the tracker too, so a value change needs no old-value bookkeeping. The generated `remove_from_{scope}_{index}` methods accept an optional field value (defaulting to the current one, so the single-argument call is unchanged) which is how cleanup passes the recorded value. (#282)
|
|
10
|
+
- **Instance-scoped index cleanup with a shared index name**: a tracker entry is now matched on scope class *and* index name. When two scope classes declared the same `index_name`, the first-declared relationship always won, so cleanup built a stub of the wrong class and mutated the wrong key. (#282)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Instance-scoped index writes reject untrackable scopes**: `add_to_*`/`update_in_*` raise `Familia::NoIdentifier` when the scope instance has no identifier, and `ArgumentError` when the scope class's `identifier_field` is not a Symbol or String. Both are checked before the index write, so a rejected call leaves no partial state. The identifier-field restriction exists because `destroy!` must rebuild the scope from its identifier alone inside a MULTI, where reads return futures — a scope that cannot be rebuilt that way would leave an index entry nothing could clean up. (#282)
|
|
15
|
+
|
|
16
|
+
### AI Assistance
|
|
17
|
+
|
|
18
|
+
- The stored-value tracker design, the scope-config-aware entry lookup, the save-path refresh wiring, and the accompanying tests were developed with AI assistance.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Fixed
|
|
2
|
+
-----
|
|
3
|
+
|
|
4
|
+
- A record saved under a reused identifier no longer inherits the previous
|
|
5
|
+
record's index tracker. ``delete!`` removes only the main object hash, so the
|
|
6
|
+
instance-scoped index tracker (``_idx_scopes``) survived it — and the next
|
|
7
|
+
``save`` under the same identifier replayed the stale entries, silently
|
|
8
|
+
joining the new record to every scope the *previous* record had been added to
|
|
9
|
+
(or raising ``Familia::RecordExistsError`` when the new value collided with
|
|
10
|
+
another record's entry). Save now detects the staleness — tracker entries can
|
|
11
|
+
only outlive the object hash when a previous incarnation was ``delete!``'d or
|
|
12
|
+
expired, since ``add_to_*`` refuses never-saved records — and prunes them
|
|
13
|
+
inside the save transaction: ``remove_from_*`` is replayed with the recorded
|
|
14
|
+
values, clearing the dead incarnation's index entries (previously orphaned
|
|
15
|
+
forever) along with the tracker itself. The new record starts with no
|
|
16
|
+
instance-scoped memberships; ``add_to_*`` remains the explicit opt-in.
|
|
17
|
+
``save_if_not_exists!`` applies the same reconciliation (replacing its
|
|
18
|
+
previous behavior of re-syncing stale entries onto the resurrected record),
|
|
19
|
+
and the detection costs an EXISTS probe only when the tracker actually has
|
|
20
|
+
entries. Note that ``atomic_write`` performs no staleness detection (it has
|
|
21
|
+
no pre-transaction point to probe from), and once it has recreated the hash a
|
|
22
|
+
later ``save`` can no longer tell the inherited entries are stale — the first
|
|
23
|
+
write to a reused identifier should be a ``save``. (#365)
|
|
24
|
+
|
|
25
|
+
AI Assistance
|
|
26
|
+
-------------
|
|
27
|
+
|
|
28
|
+
- The staleness-detection design (prune-on-stale rather than a ``delete!``
|
|
29
|
+
override or an epoch marker), the save-path wiring, and the accompanying
|
|
30
|
+
tests were developed with AI assistance.
|