phronomy 0.22.0 → 0.24.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/.mutant.yml +3 -4
- data/CHANGELOG.md +215 -10
- data/CONTRIBUTING.md +81 -9
- data/README.md +15 -6
- data/VERIFY.sh +587 -0
- data/benchmark/bench_agent_invoke.rb +2 -2
- data/benchmark/bench_context_assembler.rb +39 -68
- data/benchmark/bench_regression.rb +2 -2
- data/docs/architecture/agent-context.md +174 -0
- data/docs/architecture/before-llm-input.md +78 -0
- data/docs/architecture/context-management.md +232 -0
- data/docs/architecture/knowledge-and-rag.md +130 -0
- data/docs/architecture/multi-agent-handoff.md +147 -0
- data/docs/architecture/persistence.md +186 -0
- data/docs/architecture/removed/agent-context.md +72 -0
- data/docs/architecture/security-boundaries.md +173 -0
- data/docs/architecture/tracing.md +194 -0
- data/docs/architecture.md +89 -0
- data/docs/archive/design/archived/04_api_design.md +507 -0
- data/docs/archive/design/archived/09_guardrails.md +186 -0
- data/docs/archive/design/archived/17_rails_integration.md +175 -0
- data/docs/archive/design/historical/00_design_philosophy.md +122 -0
- data/docs/archive/design/historical/01_rubyllm_evaluation.md +178 -0
- data/docs/archive/design/historical/06_design_decisions.md +143 -0
- data/docs/changelog/0.14-and-earlier.md +1 -1
- data/docs/decisions/001-rubyllm-as-provider-layer.md +6 -1
- data/docs/decisions/002-workflow-context-immutability.md +26 -1
- data/docs/decisions/006-no-built-in-guardrails.md +2 -1
- data/docs/decisions/012-canonical-execution-log-and-context-policy.md +120 -38
- data/docs/decisions/014-unified-persistence-durable-state.md +9 -2
- data/docs/decisions/016-semantic-multi-agent-handoff.md +114 -0
- data/docs/decisions/017-design-authority-and-adr-governance.md +200 -0
- data/docs/decisions/018-durability-guarantees-and-failure-model.md +488 -0
- data/docs/decisions/019-filter-contract-and-security-boundaries.md +229 -0
- data/docs/decisions/020-canonical-workflow-instance-identity.md +177 -0
- data/docs/decisions/021-generic-agent-invocation-identity-removal.md +119 -0
- data/docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md +193 -0
- data/docs/decisions/023-fsm-session-incarnation-identity-and-routing.md +139 -0
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +188 -0
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +249 -0
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +257 -0
- data/docs/decisions/027-llm-adapter-provider-boundary.md +93 -0
- data/docs/decisions/028-preparing-recovery-replay-contract.md +106 -0
- data/docs/decisions/029-semantic-completion-and-application-effect-boundary.md +220 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +235 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +301 -0
- data/docs/decisions/README.md +176 -0
- data/docs/design/durable-semantic-coordination/CHANGELOG_V2_REVISION_2.md +33 -0
- data/docs/design/durable-semantic-coordination/CONTINUATION_DECISION_REFACTOR.md +191 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +862 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +106 -0
- data/docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md +179 -0
- data/docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md +302 -0
- data/docs/features.md +65 -11
- data/docs/getting-started.md +77 -45
- data/docs/migrations/0.19.md +14 -7
- data/docs/migrations/0.22.md +390 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +65 -0
- data/docs/persistence-backends.md +129 -40
- data/docs/runtime-and-concurrency.md +227 -33
- data/examples/README.md +13 -0
- data/lib/phronomy/agent/agent_execution.rb +21 -17
- data/lib/phronomy/agent/agent_invocation.rb +288 -93
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
- data/lib/phronomy/agent/agent_root.rb +3 -3
- data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
- data/lib/phronomy/agent/async_event_api.rb +162 -72
- data/lib/phronomy/agent/base.rb +416 -181
- data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
- data/lib/phronomy/agent/context_assembler.rb +437 -178
- data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
- data/lib/phronomy/agent/context_plan.rb +18 -13
- data/lib/phronomy/agent/context_plan_validator.rb +246 -88
- data/lib/phronomy/agent/context_policies/default.rb +123 -34
- data/lib/phronomy/agent/context_policy.rb +109 -3
- data/lib/phronomy/agent/context_policy_input.rb +244 -0
- data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
- data/lib/phronomy/agent/exact_execution.rb +153 -0
- data/lib/phronomy/agent/execution_cancellation.rb +25 -0
- data/lib/phronomy/agent/execution_coordinator.rb +2417 -572
- data/lib/phronomy/agent/handoff.rb +43 -0
- data/lib/phronomy/agent/handoff_capability_factory.rb +45 -0
- data/lib/phronomy/agent/handoff_context.rb +120 -0
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +143 -0
- data/lib/phronomy/agent/handoff_policy.rb +143 -0
- data/lib/phronomy/agent/handoff_projection.rb +207 -0
- data/lib/phronomy/agent/handoff_request.rb +45 -0
- data/lib/phronomy/agent/handoff_runner.rb +178 -0
- data/lib/phronomy/agent/handoff_state.rb +43 -0
- data/lib/phronomy/agent/journal_record.rb +17 -4
- data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
- data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
- data/lib/phronomy/agent/llm_operation_result.rb +12 -7
- data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
- data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
- data/lib/phronomy/agent/recovery_coordinator/continuation.rb +174 -0
- data/lib/phronomy/agent/recovery_coordinator/installation.rb +381 -0
- data/lib/phronomy/agent/recovery_coordinator/resolution.rb +511 -0
- data/lib/phronomy/agent/recovery_coordinator.rb +217 -0
- data/lib/phronomy/agent/recovery_support.rb +504 -0
- data/lib/phronomy/agent/ruby_llm_materializer.rb +16 -13
- data/lib/phronomy/agent/selection/candidate.rb +53 -0
- data/lib/phronomy/agent/selection/constraint.rb +49 -0
- data/lib/phronomy/agent/shared_state.rb +38 -1
- data/lib/phronomy/agent/tool_approval_request.rb +33 -5
- data/lib/phronomy/agent/tool_definition_set.rb +49 -3
- data/lib/phronomy/agent/tool_invocation.rb +339 -103
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
- data/lib/phronomy/agent.rb +20 -2
- data/lib/phronomy/agent_already_exists_error.rb +5 -0
- data/lib/phronomy/agent_purged_error.rb +5 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
- data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
- data/lib/phronomy/engine/event_loop.rb +622 -63
- data/lib/phronomy/engine/fsm_session.rb +194 -21
- data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
- data/lib/phronomy/engine/runtime/team_ownership_registry.rb +77 -0
- data/lib/phronomy/engine/runtime.rb +92 -20
- data/lib/phronomy/generator_verifier.rb +12 -14
- data/lib/phronomy/invocation_context.rb +9 -29
- data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +134 -0
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
- data/lib/phronomy/multi_agent/orchestrator.rb +71 -26
- data/lib/phronomy/multi_agent/team_coordinator.rb +473 -125
- data/lib/phronomy/multi_agent/team_execution.rb +44 -0
- data/lib/phronomy/multi_agent/team_root.rb +41 -0
- data/lib/phronomy/persistence/durable_codec.rb +706 -0
- data/lib/phronomy/persistence/durable_record.rb +117 -0
- data/lib/phronomy/persistence/in_memory.rb +471 -133
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
- data/lib/phronomy/persistence/repository_facades.rb +535 -0
- data/lib/phronomy/persistence.rb +174 -40
- data/lib/phronomy/recovery.rb +186 -0
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +2 -1
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +137 -0
- data/lib/phronomy/testing/persistence_contract.rb +5 -0
- data/lib/phronomy/tools/agent.rb +1 -1
- data/lib/phronomy/tracing/automatic.rb +176 -0
- data/lib/phronomy/tracing/base.rb +11 -2
- data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +3 -6
- data/lib/phronomy/workflow_context.rb +14 -5
- data/lib/phronomy/workflow_recovery.rb +123 -0
- data/lib/phronomy/workflow_runner.rb +468 -256
- data/lib/phronomy.rb +6 -0
- data/scripts/api_snapshot.rb +12 -0
- data/sig/phronomy/agent.rbs +209 -7
- data/sig/phronomy/handoff.rbs +41 -0
- data/sig/phronomy/multi_agent.rbs +35 -0
- data/sig/phronomy/persistence.rbs +123 -4
- data/sig/phronomy/runtime.rbs +1 -4
- data/sig/phronomy/workflow.rbs +2 -2
- data/sig/phronomy.rbs +10 -0
- metadata +84 -18
- data/examples/workflows/agent_event_mapping.rb +0 -101
- data/examples/workflows/generic_task_event_mapping.rb +0 -66
- data/lib/phronomy/agent/activation_registry.rb +0 -28
- data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
- data/lib/phronomy/agent/context_candidate.rb +0 -47
- data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
- data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
- data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
- data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
- data/lib/phronomy/agent/context_policy_registry.rb +0 -46
- data/lib/phronomy/agent/context_request.rb +0 -35
- data/lib/phronomy/agent/context_selection_unit.rb +0 -38
- data/lib/phronomy/agent/derived_content_spec.rb +0 -34
- data/lib/phronomy/agent/runner.rb +0 -97
- data/lib/phronomy/multi_agent/handoff.rb +0 -64
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
class Persistence
|
|
5
|
+
module Migration
|
|
6
|
+
# Explicit logical conversion from the pre-S3 unversioned representations
|
|
7
|
+
# present at the ACS-06 fixed base to the initial 0.1 durable formats.
|
|
8
|
+
#
|
|
9
|
+
# This module does not scan a database or write records. A backend-specific
|
|
10
|
+
# migration runner is responsible for physical enumeration, transactions,
|
|
11
|
+
# and replacement. Normal Runtime load never calls these methods.
|
|
12
|
+
#
|
|
13
|
+
# @api public
|
|
14
|
+
module InitialFormatMigration
|
|
15
|
+
PRE_S3_AGENT_ROOT_KEYS = Phronomy::Agent::AgentRoot::ATTRIBUTES.map(&:to_s).freeze
|
|
16
|
+
PRE_S3_AGENT_EXECUTION_KEYS = Phronomy::Agent::AgentExecution::ATTRIBUTES.map(&:to_s).freeze
|
|
17
|
+
PRE_S3_JOURNAL_KEYS = Phronomy::Agent::JournalRecord::ATTRIBUTES.map(&:to_s).freeze
|
|
18
|
+
PRE_S3_LLM_CALL_KEYS = Phronomy::Agent::LLMCallRecord::ATTRIBUTES.map(&:to_s).freeze
|
|
19
|
+
PRE_S3_APPROVAL_REQUIRED_KEYS = %w[id items created_at].freeze
|
|
20
|
+
PRE_S3_APPROVAL_PARENT_KEYS = %w[execution_id agent_invocation_id].freeze
|
|
21
|
+
PRE_S3_APPROVAL_OPTIONAL_KEYS = %w[approved].freeze
|
|
22
|
+
PRE_S3_APPROVAL_ITEM_KEYS = %w[
|
|
23
|
+
tool_invocation_id tool_call_id tool_name arguments facts reason origin metadata
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def agent_root(hash)
|
|
29
|
+
source = stringify_keys(hash)
|
|
30
|
+
validate_exact_keys!(
|
|
31
|
+
source,
|
|
32
|
+
PRE_S3_AGENT_ROOT_KEYS,
|
|
33
|
+
label: "pre-S3 AgentRoot"
|
|
34
|
+
)
|
|
35
|
+
DurableCodec.encode_agent_root(
|
|
36
|
+
Phronomy::Agent::AgentRoot.from_h(source)
|
|
37
|
+
)
|
|
38
|
+
rescue Phronomy::Persistence::SerializationError
|
|
39
|
+
raise
|
|
40
|
+
rescue => error
|
|
41
|
+
migration_error("AgentRoot", error)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def agent_execution(hash)
|
|
45
|
+
source = stringify_keys(hash)
|
|
46
|
+
validate_exact_keys!(
|
|
47
|
+
source,
|
|
48
|
+
PRE_S3_AGENT_EXECUTION_KEYS,
|
|
49
|
+
label: "pre-S3 AgentExecution"
|
|
50
|
+
)
|
|
51
|
+
source["working_records"] = Array(source.fetch("working_records")).map.with_index do |record, index|
|
|
52
|
+
normalize_legacy_journal_record(
|
|
53
|
+
record,
|
|
54
|
+
label: "pre-S3 AgentExecution working_records[#{index}]"
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
source["llm_calls"] = Array(source.fetch("llm_calls")).map.with_index do |call, index|
|
|
58
|
+
normalized = stringify_keys(call)
|
|
59
|
+
validate_exact_keys!(
|
|
60
|
+
normalized,
|
|
61
|
+
PRE_S3_LLM_CALL_KEYS,
|
|
62
|
+
label: "pre-S3 AgentExecution llm_calls[#{index}]"
|
|
63
|
+
)
|
|
64
|
+
normalized
|
|
65
|
+
end
|
|
66
|
+
source["approval_request"] = normalize_legacy_approval_request(
|
|
67
|
+
source["approval_request"],
|
|
68
|
+
execution_id: source.fetch("execution_id")
|
|
69
|
+
)
|
|
70
|
+
execution = Phronomy::Agent::AgentExecution.from_h(source)
|
|
71
|
+
DurableCodec.encode_agent_execution(execution)
|
|
72
|
+
rescue Phronomy::Persistence::SerializationError
|
|
73
|
+
raise
|
|
74
|
+
rescue => error
|
|
75
|
+
migration_error("AgentExecution", error)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def journal_record(hash)
|
|
79
|
+
source = normalize_legacy_journal_record(hash, label: "pre-S3 JournalRecord")
|
|
80
|
+
DurableCodec.encode_journal_record(
|
|
81
|
+
Phronomy::Agent::JournalRecord.from_h(source)
|
|
82
|
+
)
|
|
83
|
+
rescue Phronomy::Persistence::SerializationError
|
|
84
|
+
raise
|
|
85
|
+
rescue => error
|
|
86
|
+
migration_error("JournalRecord", error)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def workflow_state(workflow_instance_id:, revision:, snapshot:)
|
|
90
|
+
DurableCodec.encode_workflow_state(
|
|
91
|
+
workflow_instance_id: workflow_instance_id,
|
|
92
|
+
workflow_revision: Integer(revision),
|
|
93
|
+
snapshot: snapshot
|
|
94
|
+
)
|
|
95
|
+
rescue Phronomy::Persistence::SerializationError
|
|
96
|
+
raise
|
|
97
|
+
rescue => error
|
|
98
|
+
migration_error("Workflow state", error)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# LLMInputManifest is a ContentStore codec boundary, not a
|
|
102
|
+
# Persistence::DurableRecord. Its pre-CG-07 integer version 1 is migrated
|
|
103
|
+
# explicitly to the pre-1.0 string version "0.1".
|
|
104
|
+
def llm_input_manifest(hash)
|
|
105
|
+
source = stringify_keys(hash)
|
|
106
|
+
old_version = source.fetch("version")
|
|
107
|
+
unless old_version == 1 || old_version == "1"
|
|
108
|
+
raise Phronomy::Persistence::SerializationError,
|
|
109
|
+
"unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
|
|
110
|
+
end
|
|
111
|
+
source["version"] = Phronomy::Agent::LLMInputManifest::VERSION
|
|
112
|
+
Phronomy::Agent::LLMInputManifest.from_h(source).to_h
|
|
113
|
+
rescue Phronomy::Persistence::SerializationError
|
|
114
|
+
raise
|
|
115
|
+
rescue => error
|
|
116
|
+
migration_error("LLMInputManifest", error)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def normalize_legacy_journal_record(hash, label:)
|
|
120
|
+
source = stringify_keys(hash)
|
|
121
|
+
validate_allowed_keys!(
|
|
122
|
+
source,
|
|
123
|
+
required_keys: PRE_S3_JOURNAL_KEYS,
|
|
124
|
+
optional_keys: ["correlation_id"],
|
|
125
|
+
label: label
|
|
126
|
+
)
|
|
127
|
+
source.delete("correlation_id")
|
|
128
|
+
source
|
|
129
|
+
end
|
|
130
|
+
private_class_method :normalize_legacy_journal_record
|
|
131
|
+
|
|
132
|
+
def normalize_legacy_approval_request(request, execution_id:)
|
|
133
|
+
return nil unless request
|
|
134
|
+
|
|
135
|
+
source = stringify_keys(request)
|
|
136
|
+
validate_allowed_keys!(
|
|
137
|
+
source,
|
|
138
|
+
required_keys: PRE_S3_APPROVAL_REQUIRED_KEYS,
|
|
139
|
+
optional_keys: PRE_S3_APPROVAL_PARENT_KEYS + PRE_S3_APPROVAL_OPTIONAL_KEYS,
|
|
140
|
+
label: "pre-S3 approval_request"
|
|
141
|
+
)
|
|
142
|
+
parents = PRE_S3_APPROVAL_PARENT_KEYS.select { |key| source.key?(key) }
|
|
143
|
+
unless parents.length == 1
|
|
144
|
+
raise Phronomy::Persistence::SerializationError,
|
|
145
|
+
"pre-S3 approval_request must contain exactly one parent identity, got #{parents.inspect}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
items = source.fetch("items")
|
|
149
|
+
unless items.is_a?(Array) && !items.empty?
|
|
150
|
+
raise Phronomy::Persistence::SerializationError,
|
|
151
|
+
"pre-S3 approval_request items must be a non-empty Array"
|
|
152
|
+
end
|
|
153
|
+
source["items"] = items.map.with_index do |item, index|
|
|
154
|
+
normalized = stringify_keys(item)
|
|
155
|
+
validate_exact_keys!(
|
|
156
|
+
normalized,
|
|
157
|
+
PRE_S3_APPROVAL_ITEM_KEYS,
|
|
158
|
+
label: "pre-S3 approval_request items[#{index}]"
|
|
159
|
+
)
|
|
160
|
+
normalized
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if source.key?("agent_invocation_id")
|
|
164
|
+
source.delete("agent_invocation_id")
|
|
165
|
+
source["execution_id"] = execution_id.to_s
|
|
166
|
+
elsif source.fetch("execution_id").to_s != execution_id.to_s
|
|
167
|
+
raise Phronomy::Persistence::SerializationError,
|
|
168
|
+
"pre-S3 approval_request execution_id does not match AgentExecution"
|
|
169
|
+
end
|
|
170
|
+
source
|
|
171
|
+
end
|
|
172
|
+
private_class_method :normalize_legacy_approval_request
|
|
173
|
+
|
|
174
|
+
def validate_allowed_keys!(hash, required_keys:, optional_keys:, label:)
|
|
175
|
+
actual = hash.keys.sort
|
|
176
|
+
missing = required_keys.sort - actual
|
|
177
|
+
unknown = actual - (required_keys + optional_keys).sort
|
|
178
|
+
return hash if missing.empty? && unknown.empty?
|
|
179
|
+
|
|
180
|
+
details = []
|
|
181
|
+
details << "missing=#{missing.inspect}" unless missing.empty?
|
|
182
|
+
details << "unknown=#{unknown.inspect}" unless unknown.empty?
|
|
183
|
+
raise Phronomy::Persistence::SerializationError,
|
|
184
|
+
"#{label} schema mismatch (#{details.join(", ")})"
|
|
185
|
+
end
|
|
186
|
+
private_class_method :validate_allowed_keys!
|
|
187
|
+
|
|
188
|
+
def validate_exact_keys!(hash, expected_keys, label:)
|
|
189
|
+
validate_allowed_keys!(
|
|
190
|
+
hash,
|
|
191
|
+
required_keys: expected_keys,
|
|
192
|
+
optional_keys: [],
|
|
193
|
+
label: label
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
private_class_method :validate_exact_keys!
|
|
197
|
+
|
|
198
|
+
def stringify_keys(hash)
|
|
199
|
+
unless hash.is_a?(Hash)
|
|
200
|
+
raise Phronomy::Persistence::SerializationError,
|
|
201
|
+
"migration input must be a Hash"
|
|
202
|
+
end
|
|
203
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
204
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
205
|
+
raise Phronomy::Persistence::SerializationError,
|
|
206
|
+
"migration key must be String or Symbol, got #{key.class}"
|
|
207
|
+
end
|
|
208
|
+
string_key = key.to_s
|
|
209
|
+
if result.key?(string_key)
|
|
210
|
+
raise Phronomy::Persistence::SerializationError,
|
|
211
|
+
"duplicate migration key after normalization: #{string_key.inspect}"
|
|
212
|
+
end
|
|
213
|
+
result[string_key] = value
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
private_class_method :stringify_keys
|
|
217
|
+
|
|
218
|
+
def migration_error(label, error)
|
|
219
|
+
raise Phronomy::Persistence::SerializationError,
|
|
220
|
+
"cannot migrate pre-S3 #{label}: #{error.class}: #{error.message}"
|
|
221
|
+
end
|
|
222
|
+
private_class_method :migration_error
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|