familia 2.0.0.pre17 → 2.0.0.pre18
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/CHANGELOG.rst +60 -0
- data/CLAUDE.md +9 -2
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/bin/irb +1 -1
- data/docs/guides/core-field-system.md +48 -26
- data/docs/migrating/v2.0.0-pre18.md +58 -0
- data/docs/qodo-merge-compliance.md +96 -0
- data/lib/familia/base.rb +0 -2
- data/lib/familia/connection/middleware.rb +58 -4
- data/lib/familia/connection.rb +1 -1
- data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
- data/lib/familia/data_type/serialization.rb +5 -5
- data/lib/familia/data_type.rb +2 -2
- data/lib/familia/encryption/encrypted_data.rb +12 -2
- data/lib/familia/encryption/manager.rb +11 -4
- data/lib/familia/features/autoloader.rb +3 -1
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +41 -27
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/horreum/database_commands.rb +1 -1
- data/lib/familia/horreum/definition.rb +6 -37
- data/lib/familia/horreum/management.rb +17 -12
- data/lib/familia/horreum/persistence.rb +1 -1
- data/lib/familia/horreum/serialization.rb +91 -73
- data/lib/familia/horreum.rb +10 -6
- data/lib/familia/identifier_extractor.rb +60 -0
- data/lib/familia/logging.rb +271 -112
- data/lib/familia/refinements.rb +0 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -2
- data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -14
- data/pr_agent.toml +31 -0
- data/pr_compliance_checklist.yaml +45 -0
- data/try/edge_cases/empty_identifiers_try.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +31 -31
- data/try/edge_cases/json_serialization_try.rb +2 -2
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
- data/try/edge_cases/race_conditions_try.rb +1 -1
- data/try/edge_cases/reserved_keywords_try.rb +1 -1
- data/try/edge_cases/string_coercion_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
- data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
- data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
- data/try/features/encrypted_fields/memory_security_try.rb +1 -1
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
- data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
- data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
- data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
- data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
- data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
- data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
- data/try/features/expiration/expiration_try.rb +1 -1
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +1 -1
- data/try/features/feature_improvements_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_try.rb +1 -1
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/real_feature_integration_try.rb +17 -14
- data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
- data/try/features/relationships/indexing_try.rb +6 -1
- data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
- data/try/features/relationships/participation_commands_verification_try.rb +4 -4
- data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
- data/try/features/relationships/participation_reverse_index_try.rb +1 -1
- data/try/features/relationships/relationships_api_changes_try.rb +1 -1
- data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
- data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
- data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
- data/try/features/relationships/relationships_performance_try.rb +1 -1
- data/try/features/relationships/relationships_performance_working_try.rb +1 -1
- data/try/features/relationships/relationships_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_try.rb +1 -1
- data/try/features/transient_fields/redacted_string_try.rb +1 -1
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +1 -1
- data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
- data/try/{core → integration/connection}/isolated_dbclient_try.rb +1 -1
- data/try/integration/connection/middleware_reconnect_try.rb +87 -0
- data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
- data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
- data/try/{core → integration/connection}/pools_try.rb +1 -1
- data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
- data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
- data/try/{core → integration}/create_method_try.rb +1 -1
- data/try/integration/cross_component_try.rb +1 -1
- data/try/{core → integration}/database_consistency_try.rb +11 -8
- data/try/{core → integration}/familia_extended_try.rb +1 -1
- data/try/{core → integration}/familia_members_methods_try.rb +1 -1
- data/try/{models → integration/models}/customer_safe_dump_try.rb +1 -1
- data/try/{models → integration/models}/customer_try.rb +1 -1
- data/try/{models → integration/models}/datatype_base_try.rb +1 -1
- data/try/{models → integration/models}/familia_object_try.rb +1 -1
- data/try/{core → integration}/persistence_operations_try.rb +1 -1
- data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
- data/try/{configuration → integration}/scenarios_try.rb +1 -1
- data/try/{core → integration}/secure_identifier_try.rb +1 -1
- data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
- data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
- data/try/{helpers → support/helpers}/test_helpers.rb +12 -3
- data/try/{core → unit/core}/autoloader_try.rb +1 -1
- data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
- data/try/{core → unit/core}/connection_try.rb +1 -1
- data/try/{core → unit/core}/errors_try.rb +1 -1
- data/try/{core → unit/core}/extensions_try.rb +1 -1
- data/try/unit/core/familia_logger_try.rb +110 -0
- data/try/{core → unit/core}/familia_try.rb +1 -1
- data/try/{core → unit/core}/middleware_try.rb +41 -1
- data/try/{core → unit/core}/settings_try.rb +1 -1
- data/try/{core → unit/core}/time_utils_try.rb +1 -1
- data/try/{core → unit/core}/tools_try.rb +1 -1
- data/try/{core → unit/core}/utils_try.rb +17 -14
- data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
- data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
- data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
- data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
- data/try/{data_types → unit/data_types}/list_try.rb +1 -1
- data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_zadd_options_try.rb +1 -1
- data/try/{data_types → unit/data_types}/string_try.rb +1 -1
- data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
- data/try/{horreum → unit/horreum}/auto_indexing_on_save_try.rb +1 -1
- data/try/{horreum → unit/horreum}/base_try.rb +3 -3
- data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
- data/try/{horreum → unit/horreum}/commands_try.rb +1 -1
- data/try/{horreum → unit/horreum}/defensive_initialization_try.rb +1 -1
- data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +1 -1
- data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
- data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
- data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
- data/try/{horreum → unit/horreum}/initialization_try.rb +2 -2
- data/try/unit/horreum/json_type_preservation_try.rb +248 -0
- data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
- data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
- data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
- data/try/{horreum → unit/horreum}/settings_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
- metadata +134 -125
- data/lib/familia/distinguisher.rb +0 -85
- data/lib/familia/refinements/logger_trace.rb +0 -60
- data/try/refinements/logger_trace_methods_try.rb +0 -44
- /data/try/{debugging → support/debugging}/README.md +0 -0
- /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
- /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
- /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
- /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +0 -0
- /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
- /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5bbf0ac2fc6a1243d74f679c7027040be00783af625dd90d376637e32417394
|
4
|
+
data.tar.gz: 4b354347a0c2403490dc20fdc12f93b3e71b826cf0b2b08638f6fd884a883630
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 221116e1aa14bc7114cb51164727587dcb0e8435dd8b85bb7d14618393ef446446376fd7febf6dbc9993098e2c819b3b0aa6f3c8bedce1c965b99ec4c8832a7b
|
7
|
+
data.tar.gz: 457b0e5bfef1f203c8f2edf934d1ee37915df04c49b0cb8b097ab9732738186ba9e8e92496851699f0952426971d2d3d244e61e594719439672996a0ccde2050
|
data/CHANGELOG.rst
CHANGED
@@ -12,6 +12,66 @@ Versioning <https://semver.org/spec/v2.0.0.html>`__.
|
|
12
12
|
|
13
13
|
<!--scriv-insert-here-->
|
14
14
|
|
15
|
+
.. _changelog-2.0.0.pre18:
|
16
|
+
|
17
|
+
2.0.0.pre18 — 2025-10-05
|
18
|
+
========================
|
19
|
+
|
20
|
+
Added
|
21
|
+
-----
|
22
|
+
|
23
|
+
- Added ``Familia.reconnect!`` method to refresh connection pools with current middleware configuration. This solves issues in test suites where middleware (like DatabaseLogger) is enabled after connection pools are created. The method clears the connection chain, increments the middleware version, and clears fiber-local connections, ensuring new connections include the latest middleware. See ``lib/familia/connection/middleware.rb:81-117``.
|
24
|
+
|
25
|
+
Changed
|
26
|
+
-------
|
27
|
+
|
28
|
+
- **BREAKING**: Implemented type-preserving JSON serialization for Horreum field values. Non-string values (Integer, Boolean, Float, nil, Hash, Array) are JSON-encoded for storage and JSON-decoded on retrieval. **Strings are stored as-is without JSON encoding** to avoid double-quoting and maintain Redis baseline simplicity. Type preservation is achieved through smart deserialization: values that parse as JSON restore to their original types, otherwise remain as strings.
|
29
|
+
|
30
|
+
- **BREAKING**: Changed default Hash key format from symbols to strings throughout the codebase (``symbolize: false`` default). This eliminates ambiguity with HTTP request parameters and IndifferentHash-style implementations, providing strict adherence to JSON parsing rules and avoiding key duplication issues.
|
31
|
+
|
32
|
+
- **BREAKING**: Fixed ``initialize_with_keyword_args`` to properly handle ``false`` and ``0`` values during object initialization. Previously, falsy values were incorrectly skipped due to truthiness checks. Now uses explicit nil checking with ``fetch`` to preserve all non-nil values including ``false`` and ``0``.
|
33
|
+
|
34
|
+
- **String serialization now uses JSON encoding**: All string values are JSON-encoded during storage (wrapped in quotes) for consistent type preservation. The lenient deserializer handles both new JSON-encoded strings and legacy plain strings automatically. PR #152
|
35
|
+
|
36
|
+
Removed
|
37
|
+
-------
|
38
|
+
|
39
|
+
- **BREAKING**: Removed ``dump_method`` and ``load_method`` configuration options from ``Familia::Base`` and ``Familia::Horreum::Definition``. JSON serialization is now hard-coded for consistency and type safety. Custom serialization methods are no longer supported.
|
40
|
+
|
41
|
+
Fixed
|
42
|
+
-----
|
43
|
+
|
44
|
+
- Fixed type coercion bugs where Integer fields (e.g., ``age: 35``) became Strings (``"35"``) and Boolean fields (e.g., ``active: true``) became Strings (``"true"``) after database round-trips. All primitive types now maintain their original types through ``find_by_dbkey``, ``refresh!``, and ``batch_update`` operations.
|
45
|
+
|
46
|
+
- Fixed ``deserialize_value`` to return all JSON-parsed types instead of filtering to Hash/Array only. This enables proper deserialization of primitive types (Integer, Boolean, Float, String) from Redis storage.
|
47
|
+
|
48
|
+
- Added JSON deserialization in ``find_by_dbkey`` using existing ``initialize_with_keyword_args_deserialize_value`` helper method to maintain DRY principles and ensure loaded objects receive properly typed field values rather than raw Redis strings.
|
49
|
+
|
50
|
+
- Optimized serialization to avoid double-encoding strings - strings stored directly in Redis as-is, only non-string types use JSON encoding. This reduces storage overhead and maintains Redis's string baseline semantics.
|
51
|
+
|
52
|
+
- Fixed encrypted fields with ``category: :encrypted`` appearing in ``to_h()`` output. These fields now correctly set ``loggable: false`` to prevent accidental exposure in logs, APIs, or external interfaces. PR #152
|
53
|
+
|
54
|
+
- Fixed middleware registration to only set ``@middleware_registered`` flag when middleware is actually enabled and registered. Previously, calling ``create_dbclient`` before enabling middleware would set the flag to ``true`` without registering anything, preventing later middleware enablement from working. The fix ensures ``register_middleware_once`` only sets the flag after successful registration. See ``lib/familia/connection/middleware.rb:124-146``.
|
55
|
+
|
56
|
+
Security
|
57
|
+
--------
|
58
|
+
|
59
|
+
- Encrypted fields defined via ``field :name, category: :encrypted`` now properly excluded from ``to_h()`` serialization, matching the security behavior of ``encrypted_field``. PR #152
|
60
|
+
|
61
|
+
Documentation
|
62
|
+
-------------
|
63
|
+
|
64
|
+
- Added comprehensive type preservation test suite (``try/unit/horreum/json_type_preservation_try.rb``) with 30 test cases covering Integer, Boolean, String, Float, Hash, Array, nested structures, nil handling, empty strings, zero values, round-trip consistency, ``batch_update``, and ``refresh!`` operations.
|
65
|
+
|
66
|
+
AI Assistance
|
67
|
+
-------------
|
68
|
+
|
69
|
+
- Claude Code (claude-sonnet-4-5) provided implementation guidance, identified the ``initialize_with_keyword_args`` falsy value bug, wrote comprehensive test suite, and coordinated multi-file changes across serialization, management, and base modules.
|
70
|
+
|
71
|
+
- Issue analysis, implementation guidance, test verification, and documentation for JSON serialization changes and encrypted field security fix.
|
72
|
+
|
73
|
+
- Claude Code (Sonnet 4.5) provided architecture analysis, implementation design, and identified critical issues through the second-opinion agent. Key contributions included recommending the simplified approach without pool shutdown lifecycle management, identifying the race condition risk in clearing ``@middleware_registered``, and suggesting the use of natural pool aging instead of explicit shutdown.
|
74
|
+
|
15
75
|
.. _changelog-2.0.0.pre17:
|
16
76
|
|
17
77
|
2.0.0.pre17 — 2025-10-03
|
data/CLAUDE.md
CHANGED
@@ -154,8 +154,15 @@ end
|
|
154
154
|
|
155
155
|
### Important Implementation Notes
|
156
156
|
|
157
|
-
**Field Initialization**: Objects can be initialized with positional args (brittle) or keyword args (robust). Keyword args are recommended.
|
158
|
-
|
157
|
+
**Field Initialization**: Objects can be initialized with positional args (brittle) or keyword args (robust). Keyword args are recommended. All non-nil values including `false` and `0` are preserved during initialization.
|
158
|
+
|
159
|
+
**Serialization**: All field values are JSON-encoded for storage and JSON-decoded on retrieval to preserve Ruby types (Integer, Boolean, String, Float, Hash, Array, nil). This ensures type preservation across the Redis storage boundary. For example:
|
160
|
+
- `age: 35` (Integer) stores as `"35"` in Redis and loads back as Integer `35`
|
161
|
+
- `active: true` (Boolean) stores as `"true"` in Redis and loads back as Boolean `true`
|
162
|
+
- `metadata: {key: "value"}` (Hash) stores as JSON and loads back as Hash with proper types
|
163
|
+
|
159
164
|
**Database Key Generation**: Automatic key generation using class name, identifier, and field/type names (aka dbkey). Pattern: `classname:identifier:fieldname`
|
165
|
+
|
160
166
|
**Memory Efficiency**: Only non-nil values are stored in keystore database to optimize memory usage.
|
167
|
+
|
161
168
|
**Thread Safety**: Data types are frozen after instantiation to ensure immutability.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -438,6 +438,19 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
438
438
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
439
439
|
5. Open a Pull Request
|
440
440
|
|
441
|
+
### PR Compliance Checks
|
442
|
+
|
443
|
+
Pull requests are automatically reviewed by [Qodo Merge](https://qodo.ai) with compliance checks for:
|
444
|
+
- **Error Handling** - External API calls and database operations must have proper error handling
|
445
|
+
- **Test Coverage** - New features must include tests using the Tryouts framework
|
446
|
+
- **Changelog Fragments** - User-facing changes should include a changelog entry
|
447
|
+
- **Documentation** - API changes must update documentation
|
448
|
+
- **Backward Compatibility** - Breaking changes must be documented
|
449
|
+
- **Thread Safety** - Shared state must be properly synchronized
|
450
|
+
- **Database Key Naming** - Keys must follow Familia conventions
|
451
|
+
|
452
|
+
See [docs/qodo-merge-compliance.md](docs/qodo-merge-compliance.md) for details.
|
453
|
+
|
441
454
|
## License
|
442
455
|
|
443
456
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
data/bin/irb
CHANGED
@@ -50,17 +50,20 @@ customer.email = "admin@acme.com" # Custom method name
|
|
50
50
|
customer.name!("Updated Corp") # Fast writer (immediate DB persistence)
|
51
51
|
```
|
52
52
|
|
53
|
-
### Field
|
53
|
+
### Special Field Types
|
54
54
|
|
55
|
-
|
55
|
+
Use dedicated field methods provided by features for special field behaviors:
|
56
56
|
|
57
57
|
```ruby
|
58
58
|
class Document < Familia::Horreum
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
field :
|
63
|
-
|
59
|
+
feature :encrypted_fields
|
60
|
+
feature :transient_fields
|
61
|
+
|
62
|
+
field :title # Regular persistent field
|
63
|
+
encrypted_field :content # Encrypted storage
|
64
|
+
transient_field :api_key # Non-persistent (memory only)
|
65
|
+
field :tags # Regular field
|
66
|
+
field :metadata # Regular field
|
64
67
|
end
|
65
68
|
```
|
66
69
|
|
@@ -285,27 +288,28 @@ order.priority_urgent? # => false
|
|
285
288
|
|
286
289
|
```ruby
|
287
290
|
class Product < Familia::Horreum
|
288
|
-
|
289
|
-
|
290
|
-
field :
|
291
|
-
field :
|
291
|
+
feature :transient_fields
|
292
|
+
|
293
|
+
field :name
|
294
|
+
field :price
|
295
|
+
field :description
|
292
296
|
transient_field :temp_data
|
293
297
|
end
|
294
298
|
|
295
299
|
# Get all field names
|
296
300
|
Product.fields
|
297
|
-
# => [:name, :price, :description, :
|
301
|
+
# => [:name, :price, :description, :temp_data]
|
298
302
|
|
299
303
|
# Get field types registry
|
300
304
|
Product.field_types
|
301
305
|
# => { name: #<FieldType...>, price: #<FieldType...>, ... }
|
302
306
|
|
303
|
-
# Get fields by category
|
304
|
-
Product.fields.select { |f| Product.field_types[f].category == :
|
305
|
-
# => [:
|
307
|
+
# Get fields by category (read-only introspection)
|
308
|
+
Product.fields.select { |f| Product.field_types[f].category == :transient }
|
309
|
+
# => [:temp_data]
|
306
310
|
|
307
311
|
# Get persistent vs transient fields
|
308
|
-
Product.persistent_fields # => [:name, :price, :description
|
312
|
+
Product.persistent_fields # => [:name, :price, :description]
|
309
313
|
Product.transient_fields # => [:temp_data]
|
310
314
|
|
311
315
|
# Field method mapping (for backward compatibility)
|
@@ -313,15 +317,24 @@ Product.field_method_map
|
|
313
317
|
# => { name: :name, price: :price, secret_key: :secret_key, temp_data: :temp_data }
|
314
318
|
```
|
315
319
|
|
316
|
-
### Field
|
320
|
+
### Using Field Type Category for Introspection
|
321
|
+
|
322
|
+
The `category` method on FieldType provides read-only metadata for introspection:
|
317
323
|
|
318
324
|
```ruby
|
319
|
-
#
|
325
|
+
# Custom field type with category metadata
|
326
|
+
class SearchableFieldType < Familia::FieldType
|
327
|
+
def category
|
328
|
+
:searchable
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Features can process fields by inspecting their category
|
320
333
|
module SearchableFieldsFeature
|
321
334
|
def self.included(base)
|
322
335
|
base.extend ClassMethods
|
323
336
|
|
324
|
-
#
|
337
|
+
# Find all searchable fields by inspecting field type category
|
325
338
|
searchable_fields = base.fields.select do |field|
|
326
339
|
base.field_types[field].category == :searchable
|
327
340
|
end
|
@@ -348,11 +361,17 @@ module SearchableFieldsFeature
|
|
348
361
|
end
|
349
362
|
|
350
363
|
class Product < Familia::Horreum
|
351
|
-
feature :searchable_fields
|
364
|
+
feature :searchable_fields
|
352
365
|
|
353
|
-
field
|
354
|
-
|
355
|
-
|
366
|
+
# Use custom field type with searchable category
|
367
|
+
def self.searchable_field(name, **options)
|
368
|
+
field_type = SearchableFieldType.new(name, **options)
|
369
|
+
register_field_type(field_type)
|
370
|
+
end
|
371
|
+
|
372
|
+
searchable_field :name
|
373
|
+
searchable_field :description
|
374
|
+
field :internal_id
|
356
375
|
end
|
357
376
|
|
358
377
|
# Auto-generated search methods available
|
@@ -733,10 +752,13 @@ end
|
|
733
752
|
### 1. Choose Appropriate Field Types
|
734
753
|
|
735
754
|
```ruby
|
736
|
-
# Use
|
755
|
+
# Use dedicated field methods provided by features
|
737
756
|
class User < Familia::Horreum
|
738
|
-
|
739
|
-
|
757
|
+
feature :transient_fields
|
758
|
+
feature :encrypted_fields
|
759
|
+
|
760
|
+
field :name # Simple persistent field
|
761
|
+
field :metadata # For complex data
|
740
762
|
transient_field :temp_token # For runtime-only data
|
741
763
|
encrypted_field :api_key # For sensitive data
|
742
764
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Migrating Guide: v2.0.0-pre18
|
2
|
+
|
3
|
+
This version completes the JSON serialization implementation by removing the string-as-is optimization and fixes encrypted field visibility in serialization.
|
4
|
+
|
5
|
+
## JSON Serialization for All Types
|
6
|
+
|
7
|
+
**What Changed:**
|
8
|
+
|
9
|
+
All field values (including strings) are now JSON-encoded during storage for consistent type preservation.
|
10
|
+
|
11
|
+
**Storage Format:**
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# Before (v2.0.0-pre14):
|
15
|
+
HGET user:123 name
|
16
|
+
"John Doe" # Plain string
|
17
|
+
|
18
|
+
# After (v2.0.0-pre18):
|
19
|
+
HGET user:123 name
|
20
|
+
"\"John Doe\"" # JSON-encoded string
|
21
|
+
```
|
22
|
+
|
23
|
+
**Migration:**
|
24
|
+
|
25
|
+
No migration needed. The deserializer automatically handles:
|
26
|
+
- **New format**: `"\"value\""` → `"value"`
|
27
|
+
- **Legacy format**: `"value"` → `"value"`
|
28
|
+
|
29
|
+
**Why This Matters:**
|
30
|
+
|
31
|
+
Prevents data corruption for edge cases:
|
32
|
+
- Badge `"007"` stays `"007"` (not converted to integer `7`)
|
33
|
+
- String `"true"` stays `"true"` (not converted to boolean)
|
34
|
+
- String `"null"` stays `"null"` (not converted to `nil`)
|
35
|
+
|
36
|
+
## Encrypted Field Security Fix
|
37
|
+
|
38
|
+
**What Changed:**
|
39
|
+
|
40
|
+
Fields defined with `category: :encrypted` now correctly exclude encrypted data from `to_h()` output.
|
41
|
+
|
42
|
+
**Before:**
|
43
|
+
```ruby
|
44
|
+
field :secret, category: :encrypted
|
45
|
+
user.to_h # => {"id" => "123", "secret" => {...}} # ❌ Exposed!
|
46
|
+
```
|
47
|
+
|
48
|
+
**After:**
|
49
|
+
```ruby
|
50
|
+
field :secret, category: :encrypted
|
51
|
+
user.to_h # => {"id" => "123"} # ✅ Secure
|
52
|
+
```
|
53
|
+
|
54
|
+
Both `encrypted_field` and `field :name, category: :encrypted` now behave identically for security.
|
55
|
+
|
56
|
+
**Migration:**
|
57
|
+
|
58
|
+
No code changes needed. Review any code relying on encrypted fields appearing in `to_h()` output.
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Qodo Merge Compliance Configuration
|
2
|
+
|
3
|
+
This document describes the Qodo Merge (formerly PR-Agent) configuration for the Familia project.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Qodo Merge provides automated PR analysis, code reviews, and compliance checks. Our configuration enables two key compliance features:
|
8
|
+
|
9
|
+
1. **Codebase Duplication Compliance** - Uses RAG (Retrieval-Augmented Generation) to check for duplicate code across related repositories
|
10
|
+
2. **Custom Compliance** - Project-specific rules tailored to Familia's development practices
|
11
|
+
|
12
|
+
## Configuration Files
|
13
|
+
|
14
|
+
### pr_agent.toml
|
15
|
+
|
16
|
+
The main Qodo Merge configuration file located in the repository root. It includes:
|
17
|
+
|
18
|
+
- **Response Language**: Set to English for consistency
|
19
|
+
- **RAG Context Enrichment**: Enabled with related repositories (`delano/familia`, `delano/tryouts`, `delano/otto`)
|
20
|
+
- **Custom Compliance Path**: References our custom compliance checklist
|
21
|
+
- **Ignore Rules**: Excludes generated files and build artifacts from analysis
|
22
|
+
|
23
|
+
### pr_compliance_checklist.yaml
|
24
|
+
|
25
|
+
Custom compliance rules specific to Familia development:
|
26
|
+
|
27
|
+
#### ErrorHandling
|
28
|
+
All external API calls and database operations must have proper error handling with try-catch blocks or appropriate error handling mechanisms.
|
29
|
+
|
30
|
+
#### TestCoverage
|
31
|
+
New features must include tests using the Tryouts framework. Test files should be in the `try/` directory following the `*_try.rb` or `*.try.rb` naming convention.
|
32
|
+
|
33
|
+
#### ChangelogFragment
|
34
|
+
User-facing changes must include a changelog fragment in the `changelog.d/` directory following RST format, or provide explicit justification for omission.
|
35
|
+
|
36
|
+
#### DocumentationUpdates
|
37
|
+
API changes must be reflected in documentation, including YARD comments for new public methods or updates to the `docs/` directory.
|
38
|
+
|
39
|
+
#### BackwardCompatibility
|
40
|
+
Changes must maintain backward compatibility or document breaking changes in migration guides with deprecation warnings.
|
41
|
+
|
42
|
+
#### ThreadSafety
|
43
|
+
Code handling shared state must be thread-safe with proper synchronization or clear documentation of thread-safety assumptions.
|
44
|
+
|
45
|
+
#### DatabaseKeyNaming
|
46
|
+
Database key generation must follow Familia conventions:
|
47
|
+
- Use the configured `delim` separator (default `:`)
|
48
|
+
- Avoid reserved keywords: `ttl`, `db`, `valkey`, `redis`
|
49
|
+
- Handle empty identifiers to prevent stack overflow
|
50
|
+
|
51
|
+
## Interactive Commands
|
52
|
+
|
53
|
+
Team members can trigger on-demand Qodo Merge analysis in PR comments:
|
54
|
+
|
55
|
+
- `/analyze --review` - Run code review
|
56
|
+
- `/analyze --test` - Generate test suggestions
|
57
|
+
- `/improve` - Get improvement suggestions
|
58
|
+
- `/ask` - Ask questions about the PR
|
59
|
+
|
60
|
+
## Compliance Status
|
61
|
+
|
62
|
+
In PR comments from `@qodo-merge-pro`, you'll see:
|
63
|
+
|
64
|
+
- 🟢 Green circle - Compliance check passed
|
65
|
+
- 🔴 Red circle - Compliance check failed with details
|
66
|
+
- ⚪ White circle - Compliance check not configured (should not appear with proper configuration)
|
67
|
+
|
68
|
+
## References
|
69
|
+
|
70
|
+
- [Qodo Merge Configuration Options](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/)
|
71
|
+
- [RAG Context Enrichment Guide](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/)
|
72
|
+
- [Compliance Guide](https://qodo-merge-docs.qodo.ai/tools/compliance/)
|
73
|
+
- [Best Practices](https://docs.qodo.ai/qodo-documentation/qodo-merge/features/best-practices)
|
74
|
+
|
75
|
+
## Maintenance
|
76
|
+
|
77
|
+
### Updating Compliance Rules
|
78
|
+
|
79
|
+
To add or modify compliance rules:
|
80
|
+
|
81
|
+
1. Edit `pr_compliance_checklist.yaml`
|
82
|
+
2. Ensure YAML syntax is valid: `ruby -r yaml -e "YAML.load_file('pr_compliance_checklist.yaml')"`
|
83
|
+
3. Commit changes - they take effect immediately on new PRs
|
84
|
+
|
85
|
+
### Updating Ignore Rules
|
86
|
+
|
87
|
+
To exclude additional files from analysis:
|
88
|
+
|
89
|
+
1. Edit the `[ignore]` section in `pr_agent.toml`
|
90
|
+
2. Use glob patterns to match file paths
|
91
|
+
3. Test with new PRs to verify exclusions work as expected
|
92
|
+
|
93
|
+
## Future Improvements (Optional)
|
94
|
+
|
95
|
+
- **Centralized Configuration**: Create a `pr-agent-settings` repository with `metadata.yaml` to share configuration across all repos
|
96
|
+
- **Wiki Configuration**: Enable repo wiki and create `.pr_agent.toml` page (wiki config takes precedence over local files)
|
data/lib/familia/base.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# lib/familia/connection/middleware.rb
|
2
2
|
|
3
|
-
require_relative '../../middleware/
|
3
|
+
require_relative '../../middleware/database_logger'
|
4
4
|
|
5
5
|
module Familia
|
6
6
|
module Connection
|
@@ -19,13 +19,13 @@ module Familia
|
|
19
19
|
# Increments the middleware version, invalidating all cached connections
|
20
20
|
def increment_middleware_version!
|
21
21
|
@middleware_version += 1
|
22
|
-
Familia.trace :MIDDLEWARE_VERSION, nil, "Incremented to #{@middleware_version}"
|
22
|
+
Familia.trace :MIDDLEWARE_VERSION, nil, "Incremented to #{@middleware_version}"
|
23
23
|
end
|
24
24
|
|
25
25
|
# Sets a versioned fiber-local connection
|
26
|
-
def
|
26
|
+
def fiber_connection=(connection)
|
27
27
|
Fiber[:familia_connection] = [connection, middleware_version]
|
28
|
-
Familia.trace :FIBER_CONNECTION, nil, "Set with version #{middleware_version}"
|
28
|
+
Familia.trace :FIBER_CONNECTION, nil, "Set with version #{middleware_version}"
|
29
29
|
end
|
30
30
|
|
31
31
|
# Clears the fiber-local connection
|
@@ -50,24 +50,78 @@ module Familia
|
|
50
50
|
increment_middleware_version! if value
|
51
51
|
end
|
52
52
|
|
53
|
+
# Reconnects with fresh middleware registration
|
54
|
+
#
|
55
|
+
# This method is useful when middleware needs to be applied to connection pools
|
56
|
+
# that were created before middleware was enabled. It:
|
57
|
+
#
|
58
|
+
# 1. Clears the middleware registration flag to allow re-registration
|
59
|
+
# 2. Re-runs the middleware registration logic
|
60
|
+
# 3. Clears connection chain to force rebuild
|
61
|
+
# 4. Increments middleware version to invalidate cached connections
|
62
|
+
# 5. Clears fiber-local connections
|
63
|
+
#
|
64
|
+
# The next connection request will use the updated middleware configuration.
|
65
|
+
# Existing connection pools will naturally create new connections with middleware
|
66
|
+
# as old connections are cycled out.
|
67
|
+
#
|
68
|
+
# @note If no middleware is enabled, this method safely clears connection state
|
69
|
+
# but won't register any middleware until it's enabled.
|
70
|
+
#
|
71
|
+
# @example Enable middleware and reconnect
|
72
|
+
# Familia.enable_database_logging = true
|
73
|
+
# Familia.reconnect!
|
74
|
+
#
|
75
|
+
# @example In test suites
|
76
|
+
# # Test file A creates pools
|
77
|
+
# Familia.connection_provider = ->(uri) { pool.with { |c| c } }
|
78
|
+
#
|
79
|
+
# # Test file B enables middleware
|
80
|
+
# Familia.enable_database_logging = true
|
81
|
+
# Familia.reconnect! # Force new connections with middleware
|
82
|
+
#
|
83
|
+
def reconnect!
|
84
|
+
# Allow middleware to be re-registered
|
85
|
+
@middleware_registered = false
|
86
|
+
register_middleware_once
|
87
|
+
|
88
|
+
# Clear connection chain to force rebuild
|
89
|
+
@connection_chain = nil
|
90
|
+
|
91
|
+
# Increment version to invalidate all cached connections
|
92
|
+
increment_middleware_version!
|
93
|
+
|
94
|
+
# Clear fiber-local connections
|
95
|
+
clear_fiber_connection!
|
96
|
+
|
97
|
+
Familia.trace :RECONNECT, nil, 'Connection chain cleared, will rebuild with current middleware on next use'
|
98
|
+
end
|
99
|
+
|
53
100
|
private
|
54
101
|
|
55
102
|
# Registers middleware once globally, regardless of when clients are created.
|
56
103
|
# This prevents duplicate middleware registration and ensures all clients get middleware.
|
57
104
|
def register_middleware_once
|
105
|
+
# Skip if already registered
|
58
106
|
return if @middleware_registered
|
59
107
|
|
108
|
+
# Check if any middleware is enabled
|
109
|
+
return unless Familia.enable_database_logging || Familia.enable_database_counter
|
110
|
+
|
60
111
|
if Familia.enable_database_logging
|
61
112
|
DatabaseLogger.logger = Familia.logger
|
62
113
|
RedisClient.register(DatabaseLogger)
|
114
|
+
Familia.trace :MIDDLEWARE_REGISTERED, nil, 'Registered DatabaseLogger'
|
63
115
|
end
|
64
116
|
|
65
117
|
if Familia.enable_database_counter
|
66
118
|
# NOTE: This middleware uses AtomicFixnum from concurrent-ruby which is
|
67
119
|
# less contentious than Mutex-based counters. Safe for production.
|
68
120
|
RedisClient.register(DatabaseCommandCounter)
|
121
|
+
Familia.trace :MIDDLEWARE_REGISTERED, nil, 'Registered DatabaseCommandCounter'
|
69
122
|
end
|
70
123
|
|
124
|
+
# Set flag after successful registration
|
71
125
|
@middleware_registered = true
|
72
126
|
end
|
73
127
|
end
|
data/lib/familia/connection.rb
CHANGED
@@ -42,7 +42,7 @@ module Familia
|
|
42
42
|
@connection_chain = nil # Force rebuild of chain
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
# Sets the default URI for Database connections.
|
46
46
|
#
|
47
47
|
# NOTE: uri is not a property of the Settings module b/c it's not
|
48
48
|
# configured in class defintions like default_expiration or logical DB index.
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# lib/familia/data_type/
|
1
|
+
# lib/familia/data_type/database_commands.rb
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
class DataType
|
5
5
|
# Must be included in all DataType classes to provide Valkey/Redis
|
6
6
|
# commands. The class must have a dbkey method.
|
7
|
-
module
|
7
|
+
module DatabaseCommands
|
8
8
|
def move(logical_database)
|
9
9
|
dbclient.move dbkey, logical_database
|
10
10
|
end
|
@@ -11,9 +11,9 @@ module Familia
|
|
11
11
|
# @return [String, nil] The serialized representation of the value, or nil
|
12
12
|
# if serialization fails.
|
13
13
|
#
|
14
|
-
# @note When a class option is specified, it uses
|
15
|
-
#
|
16
|
-
#
|
14
|
+
# @note When a class option is specified, it uses Familia.identifier_extractor
|
15
|
+
# to extract the identifier from objects. Otherwise, it extracts identifiers
|
16
|
+
# from Familia::Base instances or class names.
|
17
17
|
#
|
18
18
|
# @example With a class option
|
19
19
|
# serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
|
@@ -31,13 +31,13 @@ module Familia
|
|
31
31
|
Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}>" if Familia.debug?
|
32
32
|
|
33
33
|
if opts[:class]
|
34
|
-
prepared = Familia.
|
34
|
+
prepared = Familia.identifier_extractor(opts[:class])
|
35
35
|
Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
|
36
36
|
end
|
37
37
|
|
38
38
|
if prepared.nil?
|
39
39
|
# Enforce strict values when no class option is specified
|
40
|
-
prepared = Familia.
|
40
|
+
prepared = Familia.identifier_extractor(val)
|
41
41
|
Familia.ld " from <#{val.class}> => <#{prepared.class}>"
|
42
42
|
end
|
43
43
|
|
data/lib/familia/data_type.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require_relative 'data_type/class_methods'
|
4
4
|
require_relative 'data_type/settings'
|
5
5
|
require_relative 'data_type/connection'
|
6
|
-
require_relative 'data_type/
|
6
|
+
require_relative 'data_type/database_commands'
|
7
7
|
require_relative 'data_type/serialization'
|
8
8
|
|
9
9
|
# Familia
|
@@ -77,7 +77,7 @@ module Familia
|
|
77
77
|
|
78
78
|
include Settings
|
79
79
|
include Connection
|
80
|
-
include
|
80
|
+
include DatabaseCommands
|
81
81
|
include Serialization
|
82
82
|
end
|
83
83
|
|
@@ -44,8 +44,18 @@ module Familia
|
|
44
44
|
new(**parsed)
|
45
45
|
end
|
46
46
|
|
47
|
-
def self.from_json(
|
48
|
-
|
47
|
+
def self.from_json(json_string_or_hash)
|
48
|
+
# Support both JSON strings (legacy) and already-parsed Hashes (v2.0 deserialization)
|
49
|
+
if json_string_or_hash.is_a?(Hash)
|
50
|
+
# Already parsed - use directly
|
51
|
+
parsed = json_string_or_hash
|
52
|
+
# Symbolize keys if they're strings
|
53
|
+
parsed = parsed.transform_keys(&:to_sym) if parsed.keys.first.is_a?(String)
|
54
|
+
new(**parsed)
|
55
|
+
else
|
56
|
+
# JSON string - validate and parse
|
57
|
+
validate!(json_string_or_hash)
|
58
|
+
end
|
49
59
|
end
|
50
60
|
|
51
61
|
# Instance methods for decryptability validation
|
@@ -32,15 +32,22 @@ module Familia
|
|
32
32
|
Familia::Encryption.secure_wipe(key) if key
|
33
33
|
end
|
34
34
|
|
35
|
-
def decrypt(
|
36
|
-
return nil if
|
35
|
+
def decrypt(encrypted_json_or_hash, context:, additional_data: nil)
|
36
|
+
return nil if encrypted_json_or_hash.nil? || (encrypted_json_or_hash.respond_to?(:empty?) && encrypted_json_or_hash.empty?)
|
37
37
|
|
38
38
|
# Increment counter immediately to track all decryption attempts, even failed ones
|
39
39
|
Familia::Encryption.derivation_count.increment
|
40
40
|
|
41
41
|
begin
|
42
|
-
|
43
|
-
|
42
|
+
# Delegate parsing and instantiation to EncryptedData.from_json
|
43
|
+
# Wrap validation errors for security (don't expose internal structure details)
|
44
|
+
begin
|
45
|
+
data = Familia::Encryption::EncryptedData.from_json(encrypted_json_or_hash)
|
46
|
+
raise EncryptionError, 'Failed to parse encrypted data' unless data
|
47
|
+
rescue EncryptionError => e
|
48
|
+
# Re-wrap validation errors with generic message for security
|
49
|
+
raise EncryptionError, "Decryption failed: #{e.message}"
|
50
|
+
end
|
44
51
|
|
45
52
|
# Validate algorithm support
|
46
53
|
provider = Registry.get(data.algorithm)
|