phronomy 0.32.0 → 0.34.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -0
  3. data/CONTRIBUTING.md +5 -5
  4. data/README.md +20 -1
  5. data/RELEASE_CHECKLIST.md +7 -1
  6. data/SECURITY.md +1 -1
  7. data/VERIFY.sh +1 -9
  8. data/docs/architecture/error-api-review.md +6 -2
  9. data/docs/architecture/structured-output-and-scope.md +8 -8
  10. data/docs/architecture/trust-acceptance-followup.md +15 -0
  11. data/docs/context-policy-budgeting.md +108 -0
  12. data/docs/decisions/016-semantic-multi-agent-handoff.md +6 -5
  13. data/docs/examples/shared_state_custom_sharing.rb +145 -0
  14. data/docs/features.md +4 -2
  15. data/docs/mcp-client.md +4 -2
  16. data/docs/migrations/cleanup-audit.md +89 -0
  17. data/docs/migrations/legacy-format-removal.md +55 -0
  18. data/docs/migrations/r8-unit6.md +4 -2
  19. data/docs/migrations/shared-state-multi-agent.md +5 -0
  20. data/docs/migrations/vector-async-clients.md +6 -1
  21. data/docs/runtime-and-concurrency.md +36 -0
  22. data/docs/shared-state-sharing.md +233 -0
  23. data/lib/phronomy/agent/base.rb +53 -0
  24. data/lib/phronomy/agent/execution/agent_invocation.rb +0 -4
  25. data/lib/phronomy/agent/execution/execution_coordinator.rb +11 -0
  26. data/lib/phronomy/agent/handoff/transfer_projection.rb +0 -9
  27. data/lib/phronomy/agent/journal/journal_record.rb +2 -2
  28. data/lib/phronomy/agent/lifecycle/hosted_invocation.rb +2 -5
  29. data/lib/phronomy/agent/recovery/recovery_rules.rb +0 -12
  30. data/lib/phronomy/agent/store.rb +3 -8
  31. data/lib/phronomy/agent/tool_execution/tool_approval_request.rb +3 -2
  32. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +9 -36
  33. data/lib/phronomy/context/context_policy.rb +13 -8
  34. data/lib/phronomy/context/context_policy_input.rb +26 -0
  35. data/lib/phronomy/context/llm_input_manifest.rb +3 -3
  36. data/lib/phronomy/engine/runtime/shutdown_result.rb +0 -5
  37. data/lib/phronomy/execution/execution.rb +0 -11
  38. data/lib/phronomy/multi_agent/shared_state/binding.rb +44 -0
  39. data/lib/phronomy/multi_agent/shared_state/member_lifecycle_error.rb +32 -0
  40. data/lib/phronomy/multi_agent/shared_state.rb +152 -10
  41. data/lib/phronomy/tool/base.rb +69 -3
  42. data/lib/phronomy/tool/schema.rb +23 -11
  43. data/lib/phronomy/version.rb +1 -1
  44. data/scripts/api_snapshot.rb +6 -3
  45. data/sig/phronomy/agent.rbs +1 -0
  46. data/sig/phronomy/context.rbs +3 -0
  47. data/sig/phronomy/multi_agent.rbs +26 -2
  48. data/sig/phronomy/tool.rbs +1 -0
  49. metadata +9 -11
  50. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +0 -9
  51. data/lib/phronomy/persistence/migration/initial_format_migration.rb +0 -226
  52. data/scripts/add_to_h_to_token_doubles.rb +0 -33
  53. data/scripts/add_to_h_unnamed_doubles.rb +0 -27
  54. data/scripts/migrate_generator_verifier_result.rb +0 -18
  55. data/scripts/migrate_spec_agent_definition.rb +0 -108
  56. data/scripts/migrate_spec_agent_definition_pass2.rb +0 -53
  57. data/scripts/migrate_spec_inline_pass3.rb +0 -24
  58. data/sig/phronomy/persistence/migration.rbs +0 -13
@@ -43,19 +43,17 @@ module Phronomy
43
43
  unless @json_schema.is_a?(Hash) && @json_schema["type"] == "object"
44
44
  raise ArgumentError, "Tool schema must declare an object"
45
45
  end
46
+ @json_schema = Phronomy::Values::Immutable.copy(@json_schema)
46
47
  @references = []
47
48
  validate_keywords!(@json_schema)
48
- options = {ref_resolver: ->(uri) { raise ArgumentError, "external Tool schema reference: #{uri}" }}
49
- unless JSONSchemer.valid_schema?(@json_schema, **options)
50
- raise ArgumentError, "invalid Tool JSON Schema"
51
- end
52
- @json_schema = Phronomy::Values::Immutable.copy(@json_schema)
53
- @validator = JSONSchemer.schema(@json_schema, **options)
54
- @references.each { |reference| @validator.ref(reference) }
55
- @references = nil
56
- freeze
57
- rescue JSONSchemer::InvalidRefPointer, JSONSchemer::InvalidRefResolution => error
58
- raise ArgumentError, "invalid local Tool schema reference: #{error.message}"
49
+ @references.freeze
50
+ compile_validator!
51
+ end
52
+
53
+ # Build an independent validator from this immutable Tool definition.
54
+ # @api private
55
+ def for_tool_instance
56
+ dup.compile_validator!
59
57
  end
60
58
 
61
59
  # @api public
@@ -99,6 +97,20 @@ module Phronomy
99
97
  end
100
98
  end
101
99
 
100
+ protected
101
+
102
+ def compile_validator!
103
+ options = {ref_resolver: ->(uri) { raise ArgumentError, "external Tool schema reference: #{uri}" }}
104
+ unless JSONSchemer.valid_schema?(@json_schema, **options)
105
+ raise ArgumentError, "invalid Tool JSON Schema"
106
+ end
107
+ @validator = JSONSchemer.schema(@json_schema, **options)
108
+ @references.each { |reference| @validator.ref(reference) }
109
+ freeze
110
+ rescue JSONSchemer::InvalidRefPointer, JSONSchemer::InvalidRefResolution => error
111
+ raise ArgumentError, "invalid local Tool schema reference: #{error.message}"
112
+ end
113
+
102
114
  private
103
115
 
104
116
  def error_message(error)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- VERSION = "0.32.0"
4
+ VERSION = "0.34.0"
5
5
  end
@@ -106,9 +106,12 @@ def snapshot_entry(klass)
106
106
  "public_class_methods" => (klass.public_methods(false) - BASELINE_CLASS_METHODS).sort
107
107
  }
108
108
  else
109
- internal_context_methods = (klass == Phronomy::InvocationContext) ?
110
- %i[__bind_execution __execution_scope] : []
111
- instance_methods = (klass.public_instance_methods - BASELINE_INSTANCE_METHODS - internal_context_methods).sort
109
+ internal_methods = case klass.name
110
+ when "Phronomy::InvocationContext" then %i[__bind_execution __execution_scope]
111
+ when "Phronomy::Tool::Schema" then %i[for_tool_instance]
112
+ else []
113
+ end
114
+ instance_methods = (klass.public_instance_methods - BASELINE_INSTANCE_METHODS - internal_methods).sort
112
115
  internal_class_methods = (klass == Phronomy::Storage::AsyncClient) ? %i[submit] : []
113
116
  class_methods = (klass.public_methods(false) - BASELINE_CLASS_METHODS - internal_class_methods).sort
114
117
  {
@@ -83,6 +83,7 @@ module Phronomy
83
83
  def self.instructions: (?untyped text) { (untyped) -> untyped } -> untyped
84
84
  | (?untyped text) -> untyped
85
85
  def self.tools: (?Hash[Class, String?] definitions) -> untyped
86
+ def self.with_tools: (Hash[Class, String?] definitions, id: String, version: Integer) -> singleton(Base)
86
87
  def self.tool_aliases: () -> Hash[Class, String]
87
88
  def self.provider: (?Symbol name) -> Symbol?
88
89
  def self.temperature: (?Float value) -> Float?
@@ -110,6 +110,8 @@ module Phronomy
110
110
  attr_reader previous_manifest: LLMInputManifest?
111
111
  attr_reader metadata: Hash[untyped, untyped]
112
112
  def initialize: (agent_id: String, ?execution_id: String?, call_sequence: Integer, call_mode: Symbol, ?instruction: Array[InstructionItem], ?knowledge: Array[KnowledgeItem], ?tools: Array[ToolItem], ?conversation: Array[Array[ConversationItem]], token_budget: untyped, ?model_config: Hash[untyped, untyped], previous_manifest: LLMInputManifest?, ?metadata: Hash[untyped, untyped]) -> void
113
+ def input_token_limit: () -> Integer?
114
+ def with_input_token_limit: (Integer limit) -> ContextPolicyInput
113
115
  end
114
116
 
115
117
  class ContextPlan
@@ -124,6 +126,7 @@ module Phronomy
124
126
  class ContextPolicy
125
127
  def call: (ContextPolicyInput input) -> ContextPlan
126
128
 
129
+ def estimate_tokens: (untyped content) -> Integer
127
130
  def instruction_item: (content: untyped, ?role: Symbol, ?kind: Symbol, ?content_format: Symbol?, ?required: bool, ?metadata: Hash[untyped, untyped]) -> ContextPolicyInput::InstructionItem
128
131
  def knowledge_item: (content: untyped, ?role: Symbol, ?kind: Symbol, ?content_format: Symbol?, ?required: bool, ?metadata: Hash[untyped, untyped]) -> ContextPolicyInput::KnowledgeItem
129
132
  def conversation_item: (content: untyped, role: Symbol, ?kind: Symbol, ?content_format: Symbol?, ?sequence: Integer?, ?required: bool, ?tool_call_id: String?, ?tool_call_ids: Array[String], ?delivery: Symbol, ?metadata: Hash[untyped, untyped]) -> ContextPolicyInput::ConversationItem
@@ -1,6 +1,29 @@
1
1
  module Phronomy
2
2
  module MultiAgent
3
3
  class SharedState
4
+ interface _SharingTools
5
+ def call: (member: singleton(::Phronomy::Agent::Base), cycle: Integer) -> Hash[singleton(::Phronomy::Tool::Base), untyped]
6
+ end
7
+
8
+ interface _SharingProvider
9
+ def open: (input: untyped, config: Hash[Symbol, untyped]) { (Binding) -> untyped } -> untyped
10
+ end
11
+
12
+ class Binding
13
+ def self.new: (store: untyped, tools: _SharingTools) -> instance
14
+ attr_reader store: untyped
15
+ def tool_definitions: (member: singleton(::Phronomy::Agent::Base), cycle: Integer) -> Hash[singleton(::Phronomy::Tool::Base), String?]
16
+ end
17
+
18
+ class MemberLifecycleError < ::Phronomy::Error
19
+ attr_reader agent: ::Phronomy::Agent::Base
20
+ attr_reader result: untyped
21
+ attr_reader reason: Symbol
22
+ attr_reader primary_error: Exception?
23
+ attr_reader release_error: Exception?
24
+ def self.new: (agent: ::Phronomy::Agent::Base, result: untyped, reason: Symbol, ?primary_error: Exception?, ?release_error: Exception?) -> instance
25
+ end
26
+
4
27
  class KnowledgeStore
5
28
  def self.new: () -> instance
6
29
  def read_all: () -> Array[Hash[Symbol, untyped]]
@@ -13,8 +36,9 @@ module Phronomy
13
36
  def self.coordination: (?untyped text) -> untyped
14
37
  def self.max_cycles: (?untyped value) -> Integer?
15
38
  def self.timeout: (?untyped value) -> Float?
16
- def self.terminate_when: () ?{ (KnowledgeStore) -> untyped } -> untyped
17
- def self.aggregate: () ?{ (KnowledgeStore) -> untyped } -> untyped
39
+ def self.terminate_when: () ?{ (untyped) -> untyped } -> untyped
40
+ def self.aggregate: () ?{ (untyped) -> untyped } -> untyped
41
+ def self.sharing: (_SharingProvider provider, id: String, version: Integer) -> Hash[Symbol, untyped]
18
42
  def invoke: (untyped input, ?config: Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
19
43
  end
20
44
 
@@ -55,6 +55,7 @@ module Phronomy
55
55
  def requires_approval?: () -> untyped
56
56
  def tool_origin: () -> Symbol
57
57
  def approval_metadata: () -> Hash[untyped, untyped]
58
+ def approval_display: (arguments: Hash[String | Symbol, untyped], facts: Hash[untyped, untyped]) -> { arguments: Hash[String | Symbol, untyped], facts: Hash[untyped, untyped] }
58
59
  def execute: (**untyped args) -> untyped
59
60
 
60
61
  private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phronomy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.0
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raizo T.C.S
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-03 00:00:00.000000000 Z
11
+ date: 2026-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -257,6 +257,7 @@ files:
257
257
  - docs/archive/design/historical/06_design_decisions.md
258
258
  - docs/async-composition.md
259
259
  - docs/changelog/0.14-and-earlier.md
260
+ - docs/context-policy-budgeting.md
260
261
  - docs/decisions/001-rubyllm-as-provider-layer.md
261
262
  - docs/decisions/002-workflow-context-immutability.md
262
263
  - docs/decisions/003-event-loop-singleton.md
@@ -333,6 +334,7 @@ files:
333
334
  - docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md
334
335
  - docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md
335
336
  - docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md
337
+ - docs/examples/shared_state_custom_sharing.rb
336
338
  - docs/execution-boundaries-preview.md
337
339
  - docs/failure-reason-codes.md
338
340
  - docs/features.md
@@ -343,8 +345,10 @@ files:
343
345
  - docs/migrations/0.19.md
344
346
  - docs/migrations/0.22.md
345
347
  - docs/migrations/0.31.md
348
+ - docs/migrations/cleanup-audit.md
346
349
  - docs/migrations/durable-semantic-coordination-v2.md
347
350
  - docs/migrations/handoff-runner-multi-agent.md
351
+ - docs/migrations/legacy-format-removal.md
348
352
  - docs/migrations/neutral-storage-spi.md
349
353
  - docs/migrations/parallel-tool-chat-removal.md
350
354
  - docs/migrations/persistence-failure-contracts.md
@@ -376,6 +380,7 @@ files:
376
380
  - docs/ordered-event-delivery.md
377
381
  - docs/persistence-backends.md
378
382
  - docs/runtime-and-concurrency.md
383
+ - docs/shared-state-sharing.md
379
384
  - docs/workflow-completion.md
380
385
  - examples/README.md
381
386
  - gemfiles/http_minimum.gemfile
@@ -558,7 +563,6 @@ files:
558
563
  - lib/phronomy/execution/runnable.rb
559
564
  - lib/phronomy/execution/runtime_shutdown_error.rb
560
565
  - lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb
561
- - lib/phronomy/execution/scheduler_reentrancy_error.rb
562
566
  - lib/phronomy/execution/task_result.rb
563
567
  - lib/phronomy/execution/timeout_error.rb
564
568
  - lib/phronomy/execution/wait_policy.rb
@@ -616,6 +620,8 @@ files:
616
620
  - lib/phronomy/multi_agent/reserved_child_admission.rb
617
621
  - lib/phronomy/multi_agent/runtime_binding/engine_environment.rb
618
622
  - lib/phronomy/multi_agent/shared_state.rb
623
+ - lib/phronomy/multi_agent/shared_state/binding.rb
624
+ - lib/phronomy/multi_agent/shared_state/member_lifecycle_error.rb
619
625
  - lib/phronomy/multi_agent/store.rb
620
626
  - lib/phronomy/multi_agent/team_busy_error.rb
621
627
  - lib/phronomy/multi_agent/team_coordinator.rb
@@ -631,7 +637,6 @@ files:
631
637
  - lib/phronomy/output_parser/structured_parser.rb
632
638
  - lib/phronomy/persistence/content_repository.rb
633
639
  - lib/phronomy/persistence/errors.rb
634
- - lib/phronomy/persistence/migration/initial_format_migration.rb
635
640
  - lib/phronomy/persistence/persistence.rb
636
641
  - lib/phronomy/persistence/save_outcome.rb
637
642
  - lib/phronomy/persistence/snapshot_comparison.rb
@@ -744,16 +749,10 @@ files:
744
749
  - lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb
745
750
  - lib/phronomy/workflow/runtime_binding/workflow_task_session.rb
746
751
  - lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb
747
- - scripts/add_to_h_to_token_doubles.rb
748
- - scripts/add_to_h_unnamed_doubles.rb
749
752
  - scripts/api_snapshot.rb
750
753
  - scripts/check_api_annotations.rb
751
754
  - scripts/check_readme_ruby.rb
752
755
  - scripts/check_readme_runnable.rb
753
- - scripts/migrate_generator_verifier_result.rb
754
- - scripts/migrate_spec_agent_definition.rb
755
- - scripts/migrate_spec_agent_definition_pass2.rb
756
- - scripts/migrate_spec_inline_pass3.rb
757
756
  - scripts/run_mutation.sh
758
757
  - scripts/storage_spi_snapshot.rb
759
758
  - sig/_private/phronomy/agent/settings.rbs
@@ -815,7 +814,6 @@ files:
815
814
  - sig/phronomy/multi_agent/team_not_found_error.rbs
816
815
  - sig/phronomy/persistence.rbs
817
816
  - sig/phronomy/persistence/errors.rbs
818
- - sig/phronomy/persistence/migration.rbs
819
817
  - sig/phronomy/persistence/transaction.rbs
820
818
  - sig/phronomy/persistence_composition.rbs
821
819
  - sig/phronomy/runtime_composition/configuration.rbs
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "event_loop_reentrancy_error"
4
-
5
- module Phronomy
6
- # Backward-compatible error class name for callers that still rescue the old
7
- # scheduler-oriented exception. New code should use EventLoopReentrancyError.
8
- class SchedulerReentrancyError < EventLoopReentrancyError; end
9
- end
@@ -1,226 +0,0 @@
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
- Phronomy::Agent::Persistence::Codec.encode_agent_root(
36
- Phronomy::Agent::AgentRoot.from_h(source)
37
- )
38
- rescue Phronomy::Storage::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
- Phronomy::Agent::Persistence::Codec.encode_agent_execution(execution)
72
- rescue Phronomy::Storage::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
- Phronomy::Agent::Persistence::Codec.encode_journal_record(
81
- Phronomy::Agent::JournalRecord.from_h(source)
82
- )
83
- rescue Phronomy::Storage::SerializationError
84
- raise
85
- rescue => error
86
- migration_error("JournalRecord", error)
87
- end
88
-
89
- def workflow_state(workflow_instance_id:, revision:, snapshot:)
90
- Phronomy::Workflow::Persistence::Codec.encode_workflow_state(
91
- workflow_instance_id: workflow_instance_id,
92
- workflow_revision: Integer(revision),
93
- snapshot: snapshot
94
- )
95
- rescue Phronomy::Storage::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
- # Storage::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::Storage::SerializationError,
109
- "unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
110
- end
111
- source["version"] = Phronomy::Context::LLMInputManifest::VERSION
112
- Phronomy::Context::LLMInputManifest.from_h(source).to_h
113
- rescue Phronomy::Storage::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::Storage::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::Storage::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::Storage::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::Storage::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::Storage::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::Storage::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::Storage::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::Storage::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
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Adds to_h to all Tokens-like doubles so ExecutionCoordinator can serialize usage.
5
-
6
- files = Dir.glob("spec/**/*.rb").sort
7
-
8
- total = 0
9
- files.each do |f|
10
- content = File.read(f)
11
- modified = content.gsub(
12
- /double\(("[^"]+"),\s*(input: \d+, output: \d+, cached: \d+, cache_creation: \d+)\)/
13
- ) do
14
- name = $1
15
- attrs = $2
16
- next $& if $&.include?("to_h")
17
-
18
- h_entries = attrs.split(",").map(&:strip).map do |pair|
19
- k, v = pair.split(":").map(&:strip)
20
- "\"#{k}\" => #{v}"
21
- end.join(", ")
22
-
23
- total += 1
24
- "double(#{name}, #{attrs}, to_h: {#{h_entries}})"
25
- end
26
-
27
- if modified != content
28
- File.write(f, modified)
29
- puts " patched: #{f}"
30
- end
31
- end
32
-
33
- puts "\nAdded to_h to #{total} Tokens doubles."
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Adds to_h to unnamed (no-string-name) Tokens doubles like double(input: 5, ...)
5
-
6
- files = Dir.glob("spec/**/*.rb").sort
7
-
8
- total = 0
9
- files.each do |f|
10
- content = File.read(f)
11
- modified = content.gsub(
12
- /double\((input: (\d+), output: (\d+), cached: (\d+), cache_creation: (\d+))\)/
13
- ) do
14
- attrs, a, b, c, d = $1, $2, $3, $4, $5
15
- next $& if $&.include?("to_h")
16
-
17
- total += 1
18
- "double(#{attrs}, to_h: {\"input\" => #{a}, \"output\" => #{b}, \"cached\" => #{c}, \"cache_creation\" => #{d}})"
19
- end
20
-
21
- if modified != content
22
- File.write(f, modified)
23
- puts " patched: #{f}"
24
- end
25
- end
26
-
27
- puts "\nAdded to_h to #{total} unnamed Tokens doubles."
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Explicit one-time conversion of a pre-structured-output Result JSON object.
5
- # Never infer old formats in the runtime reader; never overwrite the source.
6
- require_relative "../lib/phronomy"
7
-
8
- abort "Usage: ruby scripts/migrate_generator_verifier_result.rb OLD.json NEW.json" unless ARGV.length == 2
9
- source, destination = ARGV
10
- value = JSON.parse(File.read(source), allow_duplicate_key: false, allow_nan: false,
11
- allow_comments: false, allow_invalid_escape: false, allow_control_characters: false)
12
- abort "Expected an old Result object without failure" unless value.is_a?(Hash) && !value.key?("failure")
13
- value["failure"] = nil
14
- result = Phronomy::GeneratorVerifier::Result.from_h(value)
15
- File.open(destination, File::WRONLY | File::CREAT | File::EXCL) do |file|
16
- file.write(JSON.pretty_generate(result.to_h))
17
- file.write("\n")
18
- end