phronomy 0.26.0 → 0.27.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/CHANGELOG.md +40 -0
- data/README.md +4 -0
- data/VERIFY.sh +27 -27
- data/benchmark/bench_agent_invoke.rb +26 -22
- data/benchmark/bench_context_assembler.rb +4 -5
- data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
- data/docs/architecture/agent-context.md +4 -2
- data/docs/architecture/agent-transition-ownership.md +89 -0
- data/docs/architecture/before-llm-input.md +6 -0
- data/docs/architecture/context-management.md +30 -0
- data/docs/architecture/context-preparation-steps.md +90 -0
- data/docs/architecture/entry-action-and-team-wording.md +72 -0
- data/docs/architecture/execution-metadata-and-values.md +80 -0
- data/docs/architecture/generator-verifier-ownership.md +111 -0
- data/docs/architecture/multi-agent-handoff.md +8 -2
- data/docs/architecture/persistence-refactoring-plan.md +50 -0
- data/docs/architecture/persistence.md +27 -4
- data/docs/architecture/refactoring-closure.md +334 -0
- data/docs/architecture/remaining-refactoring-plan.md +374 -0
- data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
- data/docs/architecture/tool-schema-recording-gap.md +50 -0
- data/docs/architecture/tracing.md +1 -1
- data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
- data/docs/architecture.md +209 -0
- data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
- data/docs/decisions/032-storage-backend-composition.md +80 -0
- data/docs/decisions/033-domain-persistence-ownership.md +79 -0
- data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
- data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
- data/docs/decisions/036-context-contract-ownership.md +86 -0
- data/docs/decisions/037-common-definition-ownership.md +62 -0
- data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
- data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
- data/docs/decisions/040-configuration-default-composition.md +94 -0
- data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
- data/docs/decisions/042-feature-owned-execution-state.md +106 -0
- data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
- data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
- data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
- data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
- data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
- data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
- data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
- data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
- data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
- data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
- data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
- data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
- data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
- data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
- data/docs/decisions/058-neutral-storage-primitives.md +78 -0
- data/docs/decisions/README.md +35 -6
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
- data/docs/features.md +9 -3
- data/docs/getting-started.md +4 -4
- data/docs/migrations/0.15.md +5 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
- data/docs/migrations/handoff-runner-multi-agent.md +35 -0
- data/docs/migrations/neutral-storage-spi.md +43 -0
- data/docs/migrations/parallel-tool-chat-removal.md +49 -0
- data/docs/migrations/shared-state-multi-agent.md +44 -0
- data/docs/migrations/storage-backend-composition.md +117 -0
- data/docs/migrations/storage-transaction-boundaries.md +74 -0
- data/docs/persistence-backends.md +162 -204
- data/docs/runtime-and-concurrency.md +117 -32
- data/lib/phronomy/agent/api/agent.rb +17 -0
- data/lib/phronomy/agent/async_event_api.rb +2 -2
- data/lib/phronomy/agent/base.rb +51 -241
- data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
- data/lib/phronomy/agent/context/capability/base.rb +67 -26
- data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
- data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
- data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
- data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
- data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
- data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
- data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
- data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
- data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
- data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
- data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
- data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
- data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
- data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
- data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
- data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +19 -95
- data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
- data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +9 -10
- data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
- data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
- data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
- data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
- data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
- data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
- data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
- data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +20 -71
- data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
- data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
- data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
- data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
- data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
- data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
- data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
- data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
- data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
- data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
- data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
- data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
- data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
- data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
- data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
- data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
- data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
- data/lib/phronomy/agent/persistence/codec.rb +358 -0
- data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
- data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
- data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
- data/lib/phronomy/agent/persistence/queries.rb +61 -0
- data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
- data/lib/phronomy/agent/persistence/watermark.rb +27 -0
- data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +32 -37
- data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
- data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
- data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
- data/lib/phronomy/agent/selection/candidate.rb +1 -1
- data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
- data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
- data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
- data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
- data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
- data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
- data/lib/phronomy/common/configuration_error.rb +7 -0
- data/lib/phronomy/common/error.rb +5 -0
- data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
- data/lib/phronomy/common/values/serializable.rb +32 -0
- data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
- data/lib/phronomy/configuration/global_configuration.rb +26 -0
- data/lib/phronomy/content_store/storage_schema.rb +11 -0
- data/lib/phronomy/content_store/stored_contents.rb +43 -0
- data/lib/phronomy/engine/backpressure_error.rb +7 -0
- data/lib/phronomy/{blocking.rb → engine/blocking.rb} +1 -1
- data/lib/phronomy/engine/cancellation_error.rb +7 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +4 -0
- data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
- data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
- data/lib/phronomy/engine/event_loop.rb +158 -624
- data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
- data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
- data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
- data/lib/phronomy/engine/execution_receiver.rb +65 -0
- data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
- data/lib/phronomy/engine/fsm_protocol.rb +14 -0
- data/lib/phronomy/engine/fsm_session.rb +35 -28
- data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
- data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
- data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
- data/lib/phronomy/engine/runtime.rb +80 -84
- data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
- data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
- data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
- data/lib/phronomy/engine/task_result.rb +4 -0
- data/lib/phronomy/engine/timeout_error.rb +7 -0
- data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
- data/lib/phronomy/generation/generator_verifier.rb +118 -0
- data/lib/phronomy/generation/low_confidence_error.rb +14 -0
- data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
- data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
- data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
- data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
- data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
- data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
- data/lib/phronomy/llm_contract/transport_error.rb +7 -0
- data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +5 -5
- data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
- data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
- data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
- data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
- data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
- data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
- data/lib/phronomy/multi_agent/team_execution.rb +1 -1
- data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
- data/lib/phronomy/multi_agent/team_root.rb +1 -1
- data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
- data/lib/phronomy/persistence/api/persistence.rb +140 -0
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
- data/lib/phronomy/persistence_composition/repositories.rb +77 -0
- data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
- data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
- data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
- data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
- data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
- data/lib/phronomy/storage/backend.rb +101 -0
- data/lib/phronomy/storage/backends/in_memory.rb +157 -0
- data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
- data/lib/phronomy/storage/blobs.rb +31 -0
- data/lib/phronomy/storage/condition.rb +25 -0
- data/lib/phronomy/storage/condition_failed_error.rb +16 -0
- data/lib/phronomy/storage/conflict_error.rb +9 -0
- data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
- data/lib/phronomy/storage/entry.rb +33 -0
- data/lib/phronomy/storage/guard_ref.rb +13 -0
- data/lib/phronomy/storage/not_found_error.rb +9 -0
- data/lib/phronomy/storage/record_codec.rb +177 -0
- data/lib/phronomy/storage/records.rb +61 -0
- data/lib/phronomy/storage/resource.rb +126 -0
- data/lib/phronomy/storage/scope.rb +25 -0
- data/lib/phronomy/storage/serialization_error.rb +9 -0
- data/lib/phronomy/storage/streams.rb +44 -0
- data/lib/phronomy/storage/transaction_error.rb +10 -0
- data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
- data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
- data/lib/phronomy/storage/validation.rb +53 -0
- data/lib/phronomy/storage/view.rb +131 -0
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
- data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
- data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
- data/lib/phronomy/testing/persistence_contract.rb +4 -0
- data/lib/phronomy/tool/contract/tool_error.rb +7 -0
- data/lib/phronomy/tools/agent.rb +1 -1
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
- data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
- data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
- data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +122 -71
- data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
- data/lib/phronomy/workflow/persistence/codec.rb +153 -0
- data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
- data/lib/phronomy/workflow/phase_machine_builder.rb +2 -2
- data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
- data/lib/phronomy.rb +52 -94
- data/scripts/api_snapshot.rb +1 -1
- data/scripts/storage_spi_snapshot.rb +36 -0
- data/sig/phronomy/agent.rbs +0 -1
- data/sig/phronomy/execution_receiver.rbs +34 -0
- data/sig/phronomy/handoff.rbs +4 -2
- data/sig/phronomy/multi_agent.rbs +18 -0
- data/sig/phronomy/persistence.rbs +7 -91
- data/sig/phronomy/storage.rbs +174 -0
- data/sig/phronomy/tool.rbs +10 -1
- metadata +230 -99
- data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
- data/lib/phronomy/agent/recovery_support.rb +0 -504
- data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
- data/lib/phronomy/agent/tool_executor.rb +0 -55
- data/lib/phronomy/generator_verifier.rb +0 -369
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
- data/lib/phronomy/persistence/durable_codec.rb +0 -706
- data/lib/phronomy/persistence/in_memory.rb +0 -690
- data/lib/phronomy/persistence/repository_facades.rb +0 -535
- data/lib/phronomy/persistence.rb +0 -276
- data/lib/phronomy/ruby_llm_patches.rb +0 -24
- data/lib/phronomy/workflow_recovery.rb +0 -123
- /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
- /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
- /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
- /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
- /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
- /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
- /data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +0 -0
- /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
- /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
- /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
- /data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +0 -0
- /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
- /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
|
@@ -1,179 +1,150 @@
|
|
|
1
|
-
# Persistence
|
|
1
|
+
# Persistence and neutral Storage SPI 2
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[ADR-058](decisions/058-neutral-storage-primitives.md) defines the current
|
|
4
|
+
extension contract. This is an intentional breaking replacement of the old
|
|
5
|
+
fixed-repository Backend SPI. Application code keeps `Persistence.new(backend:)`,
|
|
6
|
+
`Persistence.in_memory`, its eight domain repositories and result queries.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
backend should not depend on Phronomy private APIs or Runtime internals.
|
|
8
|
+
## Ownership and composition
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Storage owns `Resource`, `Backend`, `View`, `Records`, `Streams`, `Blobs`, immutable
|
|
11
|
+
`Entry` values, guards, conditions and neutral exceptions. It has no Agent/Team
|
|
12
|
+
record types, active-execution policy, content digest algorithm or watermark API.
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
Agent, Team, Workflow and ContentStore declare their own resource schemas.
|
|
15
|
+
`PersistenceComposition::StorageSchema` gathers those declarations and
|
|
16
|
+
`PersistenceComposition::Repositories` assembles domain wrappers over a View.
|
|
17
|
+
The Team and Workflow schema files live beside their features in nested loader
|
|
18
|
+
roots; loading metadata does not load their runtime implementations.
|
|
19
|
+
`ContentStore::StoredContents` owns SHA-256 identity and digest verification.
|
|
20
|
+
The Agent-owned `Watermark` composes guarded revision and stream-head conditions.
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Runtime / EventLoop / ExecutionCoordinator
|
|
20
|
-
↓
|
|
21
|
-
Phronomy::Persistence synchronous Backend SPI
|
|
22
|
-
↓
|
|
23
|
-
Database / durable storage
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Persistence does not own live Agent identity, top-level Runtime admission, or
|
|
27
|
-
live execution state. In particular, a backend must not persist or reconstruct
|
|
28
|
-
the following as part of this SPI:
|
|
29
|
-
|
|
30
|
-
- Runtime Agent ownership-registry entries;
|
|
31
|
-
- EventLoop Agent top-level admission entries;
|
|
32
|
-
- EventLoop Agent execution-directory entries;
|
|
33
|
-
- `AgentInvocation`;
|
|
34
|
-
- `FSMSession`;
|
|
35
|
-
- `TaskResult` or callbacks;
|
|
36
|
-
- EventLoop queue contents;
|
|
37
|
-
- Runtime Workflow admission entries;
|
|
38
|
-
- in-flight provider operations.
|
|
39
|
-
|
|
40
|
-
Persistence operations are synchronous. Framework-owned blocking Persistence I/O
|
|
41
|
-
is submitted to the Runtime OffloadPool by Phronomy; a backend must not post
|
|
42
|
-
EventLoop events or introduce `load_async` / `save_async` variants into this
|
|
43
|
-
contract.
|
|
44
|
-
|
|
45
|
-
## Required root surface
|
|
22
|
+
InMemory receives `resources:` and supplies one Monitor/snapshot transaction
|
|
23
|
+
across the catalog. SQL reference composition supplies the same catalog and a
|
|
24
|
+
separate physical table/column mapping to the neutral driver in examples
|
|
25
|
+
`shared/storage`. Table names, columns, indexes, DurableRecord envelopes, payloads,
|
|
26
|
+
format versions and content identities remain unchanged.
|
|
46
27
|
|
|
47
|
-
|
|
28
|
+
The [S3 closure review](architecture/refactoring-closure.md) records why these
|
|
29
|
+
names and placements remain and separates applied evidence from the candidate.
|
|
48
30
|
|
|
49
|
-
|
|
50
|
-
contents
|
|
51
|
-
agents
|
|
52
|
-
journals
|
|
53
|
-
executions
|
|
54
|
-
workflow_states
|
|
55
|
-
handoff_states
|
|
56
|
-
teams
|
|
57
|
-
team_executions
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
and two root operations:
|
|
31
|
+
## Backend and View
|
|
61
32
|
|
|
62
33
|
```ruby
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
The object yielded by `transaction` is a transaction-scoped Persistence view. It
|
|
72
|
-
must respond to all eight repository accessors and
|
|
73
|
-
`assert_agent_watermark!`. It may be the Persistence instance itself, but SQL
|
|
74
|
-
backends may instead yield an object bound to a checked-out connection or
|
|
75
|
-
transaction session.
|
|
76
|
-
|
|
77
|
-
## Required capabilities
|
|
78
|
-
|
|
79
|
-
Every backend must advertise:
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
{
|
|
83
|
-
atomic_all: true,
|
|
84
|
-
atomic_admission: true,
|
|
85
|
-
optimistic_revision: true
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
`Phronomy::Persistence::REQUIRED_CAPABILITIES` is the executable definition of
|
|
90
|
-
this requirement.
|
|
91
|
-
|
|
92
|
-
### `atomic_all`
|
|
93
|
-
|
|
94
|
-
All durable repositories must be able to participate in one atomic transaction
|
|
95
|
-
domain. A transaction may change `contents`, `agents`, `journals`, `executions`,
|
|
96
|
-
`workflow_states`, `handoff_states`, `teams`, and `team_executions`, and then either commit all changes or roll them all back.
|
|
97
|
-
|
|
98
|
-
This requirement deliberately does not claim exactly-once semantics after an
|
|
99
|
-
indeterminate database/network failure. If the underlying database cannot tell
|
|
100
|
-
the caller whether a commit happened, the backend should surface the storage
|
|
101
|
-
failure rather than pretending the outcome is known.
|
|
102
|
-
|
|
103
|
-
### `atomic_admission`
|
|
104
|
-
|
|
105
|
-
This capability is a **durable Agent execution integrity defense**. It is not the
|
|
106
|
-
primary same-process Agent ownership/admission mechanism and it is not Workflow
|
|
107
|
-
distributed locking.
|
|
108
|
-
|
|
109
|
-
For one Agent, `executions.create_active` must atomically guarantee both:
|
|
110
|
-
|
|
111
|
-
```text
|
|
112
|
-
execution_id is unique
|
|
113
|
-
AND
|
|
114
|
-
no active/suspended execution already exists for agent_id
|
|
34
|
+
backend = Phronomy::Storage::Backends::InMemory.new(resources: resources)
|
|
35
|
+
backend.view.records(resource)
|
|
36
|
+
backend.view.streams(resource)
|
|
37
|
+
backend.view.blobs(resource)
|
|
38
|
+
backend.transaction { |view| ... }
|
|
39
|
+
view.check!(guards: guards, conditions: conditions)
|
|
115
40
|
```
|
|
116
41
|
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
42
|
+
The backend declares `spi_version: 2` and true values for `atomic_resources`,
|
|
43
|
+
`record_cas`, `stream_cas`, `conditional_unique`, `guarded_checks` and
|
|
44
|
+
`nested_savepoints`. Persistence validates these capabilities and the required
|
|
45
|
+
resource declarations before exposing repositories. Old duck-typed backends are
|
|
46
|
+
rejected with `UnsupportedBackendError`; there is no eight-slot compatibility
|
|
47
|
+
adapter. Public Persistence capabilities retain `atomic_all`, `atomic_admission`
|
|
48
|
+
and `optimistic_revision`, derived by composition from these primitives.
|
|
49
|
+
|
|
50
|
+
A root handle routes to the current transaction on the same backend/thread.
|
|
51
|
+
A bound view and all its handles expire on commit or rollback and reject use from
|
|
52
|
+
another thread. Explicit nested transactions use savepoints on the same SQL
|
|
53
|
+
connection, or nested InMemory snapshots. Inner success depends on outer commit;
|
|
54
|
+
an inner exception is re-raised after rollback and can be caught by the outer
|
|
55
|
+
scope. `ActiveRecord::Rollback` also propagates.
|
|
56
|
+
|
|
57
|
+
A failure during physical work marks the scope failed. Catching it inside that
|
|
58
|
+
same scope does not permit further operations or successful commit. Establish an
|
|
59
|
+
explicit inner transaction before a recoverable operation and catch outside it.
|
|
60
|
+
Input validation occurs before physical work. A successful optional read returning
|
|
61
|
+
nil is not a failed physical operation; domain required-load errors may be raised
|
|
62
|
+
after that read. Domain decode/returned-metadata failures stay inside the atomic
|
|
63
|
+
boundary so a corrupt response after a write causes rollback.
|
|
64
|
+
|
|
65
|
+
Transaction blocks must finish normally. `return`, `break` and `throw` escaping
|
|
66
|
+
them cause rollback and `TransactionError`. These exits are not commit controls.
|
|
67
|
+
Commit/rollback transport failures remain database failures; this contract does
|
|
68
|
+
not promise exactly-once external effects or infer commit certainty.
|
|
69
|
+
|
|
70
|
+
## Resource declarations
|
|
71
|
+
|
|
72
|
+
A `Resource` is an immutable value with `id`, `kind`, `attributes`,
|
|
73
|
+
`immutable_attributes`, `indexes`, `unique` and optional `guard`. Attributes use
|
|
74
|
+
`:string`, `:integer`, `:boolean` and their explicit `:nullable_*` forms. Keys and
|
|
75
|
+
text attributes use valid UTF-8 without NUL; keys are nonempty. Binary data belongs
|
|
76
|
+
to Blobs. No Proc, SQL expression or payload predicate is accepted.
|
|
77
|
+
|
|
78
|
+
Named equality indexes specify exact fields. Records may declare conditional
|
|
79
|
+
unique constraints with a symbol name, fields and equality `where` values. Null
|
|
80
|
+
unique-key fields are distinct, matching the default SQL unique-index semantics.
|
|
81
|
+
Streams have no indexed attributes; Blobs have attributes but no indexes/guards.
|
|
82
|
+
A record guard uses its key or an immutable non-null string attribute. A stream
|
|
83
|
+
guard uses its stream ID. Its anchor must be a registered Records resource.
|
|
84
|
+
Required anchors must exist; missing parents raise `NotFoundError` in all drivers.
|
|
85
|
+
|
|
86
|
+
## Records
|
|
87
|
+
|
|
88
|
+
| Operation | Contract |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `insert(key:, revision:, attributes:, record:)` | Insert only when absent; return an independent immutable `Entry::Record`. Primary duplicates and named unique failures differ. |
|
|
91
|
+
| `read(key)` / `fetch(key)` | Optional nil / required `NotFoundError`. |
|
|
92
|
+
| `replace(key:, expected_revision:, next_revision:, attributes:, record:, expected_attributes: {})` | Check existence, revision, expected attributes, immutable fields and uniqueness atomically; next revision must equal expected + 1. Replace all attributes. |
|
|
93
|
+
| `delete(key:, expected_revision: Records::UNCHECKED)` | Unchecked deletion is idempotent. A supplied revision requires existence and equality. Return nil. |
|
|
94
|
+
| `scan(index:, equals:, after: nil, limit: nil)` | Exact named-index fields, UTF-8 byte order, exclusive key cursor, positive limit or nil for all. |
|
|
95
|
+
| `delete_matching(index:, equals:)` | Delete equality matches atomically; a parent-guarded resource must constrain the guard attribute. Return nil. |
|
|
96
|
+
|
|
97
|
+
An Entry carries key, revision, attributes and an opaque `DurableRecord`.
|
|
98
|
+
The driver does not reconstruct metadata from payload. Domain wrappers verify
|
|
99
|
+
that returned identity, revision and attributes agree with decoded domain data.
|
|
100
|
+
Workflow/Handoff choose initial revision 1 and route nil expected revision to
|
|
101
|
+
insert; nil is never an unchecked update. Team terminal-to-active rejection is a
|
|
102
|
+
domain `expected_attributes: {active: true}` precondition when saving active state.
|
|
103
|
+
|
|
104
|
+
## Streams and Blobs
|
|
105
|
+
|
|
106
|
+
Streams expose `append(stream:, expected_head:, entries:)`,
|
|
107
|
+
`read(stream:, after: 0, limit: nil)`, `head(stream:)`, and `delete(stream:)`.
|
|
108
|
+
Append takes `Entry::Append(id:, record:)`, validates the entire batch before any
|
|
109
|
+
write, enforces unique entry IDs within a stream, assigns contiguous positions
|
|
110
|
+
and updates the head atomically. Empty append still checks the expected head.
|
|
111
|
+
Reads return immutable `Entry::Stream(position:, id:, record:)` values in position
|
|
112
|
+
order. Delete removes head and entries together. The domain Journal wrapper
|
|
113
|
+
preserves its existing `limit: 0` empty-result behavior without a raw zero-limit
|
|
114
|
+
operation.
|
|
115
|
+
|
|
116
|
+
Blobs expose `put_if_absent(key:, bytes:, attributes:)`, `fetch(key)` and
|
|
117
|
+
`exist?(key)`. Same bytes retain the first attributes; different bytes for an
|
|
118
|
+
existing key raise `BlobConflictError`. Entry bytes are independent immutable
|
|
119
|
+
binary strings. Blob keys are arbitrary storage keys; ContentStore adds the
|
|
120
|
+
`sha256:<digest>` contract and maps integrity failures to its own `IntegrityError`.
|
|
121
|
+
|
|
122
|
+
## Guards, conditions and errors
|
|
123
|
+
|
|
124
|
+
`GuardRef(resource:, key:)` names a stable existing parent record. `View#check!`
|
|
125
|
+
acquires guards in resource/key byte order before evaluating the closed condition
|
|
126
|
+
set: `RevisionIs`, `StreamHeadIs`, `NoRows`. A condition must include the guard
|
|
127
|
+
required by its resource and scope. PostgreSQL locks parents before child heads
|
|
128
|
+
or records; SQLite relies on the transaction/CAS/unique constraints and must not
|
|
129
|
+
claim a SELECT alone reserves a writer. Transactions spanning multiple owners
|
|
130
|
+
must establish a consistent owner lock order; database deadlocks remain database
|
|
131
|
+
errors, never optimistic conflicts.
|
|
132
|
+
|
|
133
|
+
`UniqueConstraintError < ConflictError` carries the resource ID and constraint
|
|
134
|
+
name. Agent/Team translate only their exact `one_active_owner` constraint to
|
|
135
|
+
`AgentBusyError`. Their idle checks translate their own `NoRows` condition failure.
|
|
136
|
+
`ConditionFailedError` carries the failed condition. Duplicate identities,
|
|
137
|
+
stale revisions and ordinary constraint conflicts remain `ConflictError`.
|
|
138
|
+
`NotFoundError`, `SerializationError`, `UnsupportedBackendError` retain their
|
|
139
|
+
meanings; `TransactionError` identifies invalid scope use. The removed
|
|
140
|
+
`ActiveExecutionConflictError` and `Storage::Repositories` have no aliases.
|
|
141
|
+
|
|
142
|
+
The following sections describe the retained **domain Persistence** surface.
|
|
171
143
|
|
|
172
144
|
## Contents repository
|
|
173
145
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
content-ID calculation.
|
|
146
|
+
`ContentStore::StoredContents < ContentStore::Base` supplies this domain surface
|
|
147
|
+
over neutral Blobs, with text/JSON helpers and canonical content-ID calculation.
|
|
177
148
|
|
|
178
149
|
Required primitive surface:
|
|
179
150
|
|
|
@@ -188,7 +159,7 @@ Required semantics:
|
|
|
188
159
|
- content is immutable and content-addressed;
|
|
189
160
|
- writing identical bytes is idempotent and returns the same content ID;
|
|
190
161
|
- `fetch` returns a binary `String` isolated from caller mutation;
|
|
191
|
-
- a missing content ID raises `
|
|
162
|
+
- a missing content ID raises `Storage::NotFoundError`;
|
|
192
163
|
- one content ID must never resolve to different bytes; a digest-integrity
|
|
193
164
|
violation raises `ContentStore::IntegrityError`.
|
|
194
165
|
|
|
@@ -374,10 +345,9 @@ honestly rather than converting them into `ConflictError`.
|
|
|
374
345
|
|
|
375
346
|
### Workflow value serialization
|
|
376
347
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
`Proc`, IO objects, sockets, or runtime callbacks.
|
|
348
|
+
The Workflow domain codec accepts canonical JSON-compatible snapshot values in
|
|
349
|
+
all drivers, including InMemory. Proc, IO, sockets and runtime callbacks are not
|
|
350
|
+
durable snapshot values.
|
|
381
351
|
|
|
382
352
|
A JSON/JSONB backend should document its supported value domain. A recommended
|
|
383
353
|
domain is:
|
|
@@ -391,7 +361,7 @@ Array of supported values
|
|
|
391
361
|
Hash with String/Symbol keys and supported values
|
|
392
362
|
```
|
|
393
363
|
|
|
394
|
-
If a value cannot be represented, raise `
|
|
364
|
+
If a value cannot be represented, raise `Storage::SerializationError` rather
|
|
395
365
|
than silently converting it into a lossy form. JSON backends may return String
|
|
396
366
|
keys after decoding; `WorkflowRunner` deliberately accepts String and Symbol keys
|
|
397
367
|
and normalizes them when comparing durable snapshots.
|
|
@@ -401,8 +371,8 @@ particular database backend accept arbitrary Workflow values.
|
|
|
401
371
|
|
|
402
372
|
## Durable Agent watermark
|
|
403
373
|
|
|
404
|
-
`assert_agent_watermark!` is a
|
|
405
|
-
|
|
374
|
+
`assert_agent_watermark!` is a domain Persistence operation. Agent-owned
|
|
375
|
+
`Watermark` composes a parent guard, root revision and Journal head conditions.
|
|
406
376
|
|
|
407
377
|
Phronomy uses it at durable barriers because a hydrated live Agent owns the
|
|
408
378
|
current logical state and Phronomy deliberately does not reload mutable Agent
|
|
@@ -482,9 +452,9 @@ This is a targeted migration rule for the removed generic identity field. It doe
|
|
|
482
452
|
not establish a general unknown-field or long-term codec/schema-versioning
|
|
483
453
|
policy.
|
|
484
454
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
455
|
+
Domain codecs own canonical Hash representation. The raw driver stores the
|
|
456
|
+
DurableRecord envelope and separately supplied metadata without interpreting
|
|
457
|
+
domain payload fields.
|
|
488
458
|
|
|
489
459
|
## Conformance tests
|
|
490
460
|
|
|
@@ -508,6 +478,11 @@ an Agent repository
|
|
|
508
478
|
a Journal repository
|
|
509
479
|
an Execution repository
|
|
510
480
|
a workflow state repository
|
|
481
|
+
a Handoff state repository
|
|
482
|
+
a Team repository
|
|
483
|
+
a Team execution repository
|
|
484
|
+
neutral storage primitives
|
|
485
|
+
storage transaction boundaries
|
|
511
486
|
a Persistence backend
|
|
512
487
|
```
|
|
513
488
|
|
|
@@ -518,7 +493,7 @@ require "phronomy"
|
|
|
518
493
|
require "phronomy/testing/persistence_contract"
|
|
519
494
|
|
|
520
495
|
RSpec.describe MyPersistenceBackend do
|
|
521
|
-
let(:persistence) { described_class.new(...) }
|
|
496
|
+
let(:persistence) { Phronomy::Persistence.new(backend: described_class.new(...)) }
|
|
522
497
|
|
|
523
498
|
it_behaves_like "a persistence content store"
|
|
524
499
|
it_behaves_like "an Agent repository"
|
|
@@ -532,7 +507,7 @@ RSpec.describe MyPersistenceBackend do
|
|
|
532
507
|
end
|
|
533
508
|
```
|
|
534
509
|
|
|
535
|
-
`Persistence
|
|
510
|
+
`Persistence.in_memory` is run through the same shipped contract source in
|
|
536
511
|
Phronomy CI. The files under `spec/support/shared_examples/` are compatibility
|
|
537
512
|
require wrappers only; the authoritative shared-example implementations live
|
|
538
513
|
under `lib/phronomy/testing/persistence_contract/` so the core suite and external
|
|
@@ -560,34 +535,17 @@ may use combinations of:
|
|
|
560
535
|
Backend-specific database exceptions should be translated to the Phronomy error
|
|
561
536
|
contract where their meaning is known.
|
|
562
537
|
|
|
563
|
-
##
|
|
538
|
+
## Coordination domain repositories
|
|
564
539
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
`
|
|
568
|
-
|
|
569
|
-
|
|
540
|
+
Handoff starts at revision 1 with expected revision nil; later saves advance one
|
|
541
|
+
revision. Team roots and executions begin at revision 0. Team admission retains
|
|
542
|
+
`AgentBusyError`; stale CAS and duplicate identities use `ConflictError`.
|
|
543
|
+
Missing Team/execution loads raise `NotFoundError`; absent Handoff state returns
|
|
544
|
+
nil. The unchanged record types are `phronomy.handoff_state`, `phronomy.team_root`
|
|
545
|
+
and `phronomy.team_execution`, all version `0.1`.
|
|
570
546
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
| `executions` extension | `list(agent_id, after: nil, limit: 100)` for retained active and terminal records |
|
|
577
|
-
|
|
578
|
-
Handoff starts at revision 1 with expected revision nil; later saves advance
|
|
579
|
-
exactly one revision. Team roots and executions begin at revision 0. Team
|
|
580
|
-
admission has the existing `AgentBusyError` contract for an already active owner;
|
|
581
|
-
stale CAS/duplicate identity uses `ConflictError`. Missing Team/execution loads
|
|
582
|
-
raise `NotFoundError`; absent Handoff state returns nil. Unavailable reads and
|
|
583
|
-
codec errors must propagate separately.
|
|
584
|
-
|
|
585
|
-
Lists sort IDs lexically, use an exclusive ID cursor, return at most the positive
|
|
586
|
-
integer limit, include only the requested owner, and return immutable copies.
|
|
587
|
-
Backends define retention; enumeration does not implement input correlation or
|
|
588
|
-
request deduplication. Terminal-to-active rewrites are invalid.
|
|
589
|
-
|
|
590
|
-
For F1, atomic commit does not imply a known response. Phronomy reads back the
|
|
591
|
-
same intended ID/fact before advancing. A backend must provide authoritative
|
|
592
|
-
reads/CAS; failure of readback is returned without new semantic work. No callback
|
|
593
|
-
ACK/index is part of the SPI. Existing cross-process exclusion limitations remain.
|
|
547
|
+
Backend authors should also read the [SPI 2 migration guide](migrations/neutral-storage-spi.md).
|
|
548
|
+
The Stable/Beta product API snapshot and the explicit Storage SPI 2 signature
|
|
549
|
+
snapshot are separate gates. Live PostgreSQL locking and failure tests must run
|
|
550
|
+
against the candidate core and examples revisions; earlier SPI results do not
|
|
551
|
+
satisfy this gate.
|