igniter 0.5.0 → 0.5.1
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 +3 -3
- data/README.md +162 -624
- data/bin/igniter-stack +94 -0
- data/docs/README.md +62 -0
- data/examples/README.md +74 -349
- data/examples/agent_orchestration.rb +76 -0
- data/examples/agents.rb +2 -1
- data/examples/catalog.rb +412 -0
- data/examples/consensus.rb +13 -11
- data/examples/dataflow.rb +3 -2
- data/examples/distributed_workflow.rb +1 -1
- data/examples/effects.rb +10 -9
- data/examples/incremental.rb +4 -3
- data/examples/introspection.rb +49 -0
- data/examples/invariants.rb +4 -3
- data/examples/llm_tools.rb +18 -18
- data/examples/mesh.rb +16 -14
- data/examples/mesh_discovery.rb +41 -21
- data/examples/mesh_gossip.rb +19 -17
- data/examples/reactive_auditing.rb +50 -0
- data/examples/run.rb +163 -0
- data/lib/igniter/monorepo_packages.rb +17 -0
- data/lib/igniter/stack.rb +5 -0
- data/lib/igniter.rb +33 -17
- data/packages/igniter-agents/README.md +22 -0
- data/{lib → packages/igniter-agents/lib}/igniter/agent/ref.rb +1 -0
- data/{lib → packages/igniter-agents/lib}/igniter/agent/runner.rb +12 -0
- data/{lib → packages/igniter-agents/lib}/igniter/agent.rb +6 -0
- data/{lib → packages/igniter-agents/lib}/igniter/agents/proactive_agent.rb +1 -1
- data/packages/igniter-agents/lib/igniter/agents.rb +23 -0
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/chain_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/critic_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/evaluator_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/evolution_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/observer_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/planner_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/router_agent.rb +4 -2
- data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/self_reflection_agent.rb +4 -2
- data/packages/igniter-agents/lib/igniter/ai/agents.rb +25 -0
- data/{lib → packages/igniter-agents/lib}/igniter/registry.rb +2 -0
- data/packages/igniter-agents/lib/igniter/runtime/registry_agent_adapter.rb +102 -0
- data/{lib → packages/igniter-agents/lib}/igniter/supervisor.rb +3 -0
- data/packages/igniter-agents/lib/igniter-agents.rb +7 -0
- data/packages/igniter-ai/README.md +20 -0
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/config.rb +1 -1
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/context.rb +2 -2
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/executor.rb +14 -14
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/anthropic.rb +5 -5
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/base.rb +1 -1
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/ollama.rb +4 -4
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/openai.rb +5 -5
- data/{lib/igniter → packages/igniter-ai/lib/igniter/ai}/skill/feedback.rb +4 -4
- data/{lib/igniter → packages/igniter-ai/lib/igniter/ai}/skill/output_schema.rb +6 -6
- data/packages/igniter-ai/lib/igniter/ai/skill/runtime_contract.rb +87 -0
- data/packages/igniter-ai/lib/igniter/ai/skill.rb +107 -0
- data/packages/igniter-ai/lib/igniter/ai/tool_registry.rb +79 -0
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/assemblyai.rb +8 -8
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/base.rb +3 -3
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/deepgram.rb +3 -3
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/openai.rb +3 -3
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/transcriber.rb +6 -6
- data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/transcript_result.rb +1 -1
- data/{lib/igniter/integrations/llm.rb → packages/igniter-ai/lib/igniter/ai.rb} +18 -15
- data/packages/igniter-ai/lib/igniter-ai.rb +3 -0
- data/packages/igniter-app/README.md +19 -0
- data/packages/igniter-app/lib/igniter/app/app_config.rb +43 -0
- data/packages/igniter-app/lib/igniter/app/app_host.rb +56 -0
- data/packages/igniter-app/lib/igniter/app/app_host_config.rb +27 -0
- data/packages/igniter-app/lib/igniter/app/app_host_pack.rb +13 -0
- data/{lib/igniter/application → packages/igniter-app/lib/igniter/app}/autoloader.rb +2 -2
- data/packages/igniter-app/lib/igniter/app/cluster_app_host.rb +95 -0
- data/packages/igniter-app/lib/igniter/app/cluster_app_host_config.rb +78 -0
- data/packages/igniter-app/lib/igniter/app/credentials/config_loader.rb +152 -0
- data/packages/igniter-app/lib/igniter/app/credentials/credential.rb +48 -0
- data/packages/igniter-app/lib/igniter/app/credentials/credential_policy.rb +38 -0
- data/packages/igniter-app/lib/igniter/app/credentials/events/credential_event.rb +179 -0
- data/packages/igniter-app/lib/igniter/app/credentials/events.rb +12 -0
- data/packages/igniter-app/lib/igniter/app/credentials/lease_request.rb +153 -0
- data/packages/igniter-app/lib/igniter/app/credentials/policies/ephemeral_lease_policy.rb +35 -0
- data/packages/igniter-app/lib/igniter/app/credentials/policies/local_only_policy.rb +33 -0
- data/packages/igniter-app/lib/igniter/app/credentials/policies.rb +13 -0
- data/packages/igniter-app/lib/igniter/app/credentials/store.rb +21 -0
- data/packages/igniter-app/lib/igniter/app/credentials/stores/file_store.rb +114 -0
- data/packages/igniter-app/lib/igniter/app/credentials/trail.rb +254 -0
- data/packages/igniter-app/lib/igniter/app/credentials.rb +20 -0
- data/packages/igniter-app/lib/igniter/app/dev_output_sync.rb +4 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/app_host_contributor.rb +71 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/cluster_app_host_contributor.rb +97 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/credential_contributor.rb +66 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/evolution_contributor.rb +74 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/ignite_contributor.rb +121 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/loader_contributor.rb +68 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/orchestration_contributor.rb +200 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/runtime_contributor.rb +68 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/scheduler_contributor.rb +72 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics/sdk_contributor.rb +284 -0
- data/packages/igniter-app/lib/igniter/app/diagnostics.rb +62 -0
- data/packages/igniter-app/lib/igniter/app/evolution/approval_decision.rb +115 -0
- data/packages/igniter-app/lib/igniter/app/evolution/approval_request.rb +36 -0
- data/packages/igniter-app/lib/igniter/app/evolution/plan.rb +72 -0
- data/packages/igniter-app/lib/igniter/app/evolution/planner.rb +85 -0
- data/packages/igniter-app/lib/igniter/app/evolution/result.rb +45 -0
- data/packages/igniter-app/lib/igniter/app/evolution/runner.rb +102 -0
- data/packages/igniter-app/lib/igniter/app/evolution/store.rb +21 -0
- data/packages/igniter-app/lib/igniter/app/evolution/stores/file_store.rb +241 -0
- data/packages/igniter-app/lib/igniter/app/evolution/trail.rb +108 -0
- data/packages/igniter-app/lib/igniter/app/evolution.rb +11 -0
- data/packages/igniter-app/lib/igniter/app/filesystem_loader_adapter.rb +21 -0
- data/packages/igniter-app/lib/igniter/app/generator.rb +636 -0
- data/packages/igniter-app/lib/igniter/app/generators/cluster.rb +1367 -0
- data/packages/igniter-app/lib/igniter/app/generators/dashboard.rb +152 -0
- data/packages/igniter-app/lib/igniter/app/generators/playground.rb +1227 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/README.md.erb +37 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/app.rb.erb +19 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/contexts/home_context.rb.erb +54 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/frontend/application.js.erb +3 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/spec/dashboard_app_spec.rb.erb +79 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/support/stack_overview.rb.erb +23 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/handlers/home_handler.rb.erb +27 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/home_page.arb.erb +44 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/home_page.rb.erb +56 -0
- data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/layout.arb.erb +17 -0
- data/packages/igniter-app/lib/igniter/app/host_adapter.rb +26 -0
- data/packages/igniter-app/lib/igniter/app/host_config.rb +40 -0
- data/packages/igniter-app/lib/igniter/app/host_registry.rb +43 -0
- data/packages/igniter-app/lib/igniter/app/loader_adapter.rb +15 -0
- data/packages/igniter-app/lib/igniter/app/loader_pack.rb +8 -0
- data/packages/igniter-app/lib/igniter/app/loader_registry.rb +39 -0
- data/packages/igniter-app/lib/igniter/app/observability/operator_action_handler.rb +147 -0
- data/packages/igniter-app/lib/igniter/app/observability/operator_console_handler.rb +747 -0
- data/packages/igniter-app/lib/igniter/app/observability/operator_overview_handler.rb +350 -0
- data/packages/igniter-app/lib/igniter/app/observability.rb +5 -0
- data/packages/igniter-app/lib/igniter/app/observability_pack.rb +71 -0
- data/packages/igniter-app/lib/igniter/app/operator/dispatcher.rb +40 -0
- data/packages/igniter-app/lib/igniter/app/operator/handler_registry.rb +40 -0
- data/packages/igniter-app/lib/igniter/app/operator/handler_result.rb +67 -0
- data/packages/igniter-app/lib/igniter/app/operator/handlers/base.rb +79 -0
- data/packages/igniter-app/lib/igniter/app/operator/handlers/ignite_handler.rb +108 -0
- data/packages/igniter-app/lib/igniter/app/operator/handlers/orchestration_handler.rb +33 -0
- data/packages/igniter-app/lib/igniter/app/operator/handlers.rb +5 -0
- data/packages/igniter-app/lib/igniter/app/operator/lifecycle_contract.rb +55 -0
- data/packages/igniter-app/lib/igniter/app/operator/policy.rb +157 -0
- data/packages/igniter-app/lib/igniter/app/operator.rb +17 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/action_result_builder.rb +65 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/followup_request.rb +36 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/handler_registry.rb +58 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/handlers.rb +106 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/inbox.rb +283 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/inbox_query.rb +293 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/lane_registry.rb +100 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/operator_query.rb +449 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/plan.rb +68 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/planner.rb +89 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/policies.rb +125 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/policy_registry.rb +63 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/result.rb +43 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/routing_registry.rb +43 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/runner.rb +50 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/runtime_event_query.rb +205 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/runtime_overview_builder.rb +286 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/runtime_query_overview_builder.rb +20 -0
- data/packages/igniter-app/lib/igniter/app/orchestration/runtime_result_builder.rb +23 -0
- data/packages/igniter-app/lib/igniter/app/orchestration.rb +113 -0
- data/packages/igniter-app/lib/igniter/app/runtime.rb +4 -0
- data/packages/igniter-app/lib/igniter/app/runtime_context.rb +101 -0
- data/packages/igniter-app/lib/igniter/app/runtime_pack.rb +16 -0
- data/packages/igniter-app/lib/igniter/app/scaffold_pack.rb +6 -0
- data/{lib/igniter/application → packages/igniter-app/lib/igniter/app}/scheduler.rb +2 -2
- data/packages/igniter-app/lib/igniter/app/scheduler_adapter.rb +17 -0
- data/packages/igniter-app/lib/igniter/app/scheduler_pack.rb +8 -0
- data/packages/igniter-app/lib/igniter/app/scheduler_registry.rb +39 -0
- data/packages/igniter-app/lib/igniter/app/stack.rb +1726 -0
- data/packages/igniter-app/lib/igniter/app/stack_pack.rb +3 -0
- data/packages/igniter-app/lib/igniter/app/threaded_scheduler_adapter.rb +35 -0
- data/packages/igniter-app/lib/igniter/app/yml_loader.rb +43 -0
- data/packages/igniter-app/lib/igniter/app.rb +2367 -0
- data/packages/igniter-app/lib/igniter/ignite/bootstrap_agent.rb +334 -0
- data/packages/igniter-app/lib/igniter/ignite/bootstrap_target.rb +79 -0
- data/packages/igniter-app/lib/igniter/ignite/deployment_intent.rb +82 -0
- data/packages/igniter-app/lib/igniter/ignite/ignition_agent.rb +1011 -0
- data/packages/igniter-app/lib/igniter/ignite/ignition_plan.rb +83 -0
- data/packages/igniter-app/lib/igniter/ignite/ignition_report.rb +144 -0
- data/packages/igniter-app/lib/igniter/ignite/store.rb +19 -0
- data/packages/igniter-app/lib/igniter/ignite/stores/file_store.rb +112 -0
- data/packages/igniter-app/lib/igniter/ignite/trail.rb +215 -0
- data/packages/igniter-app/lib/igniter/ignite.rb +11 -0
- data/packages/igniter-app/lib/igniter-app.rb +5 -0
- data/packages/igniter-cluster/README.md +9 -0
- data/packages/igniter-cluster/lib/igniter/cluster/agent_route_resolver.rb +58 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/cluster.rb +8 -4
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/errors.rb +3 -1
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/executors.rb +3 -1
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/node.rb +3 -1
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/read_query.rb +5 -3
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/state_machine.rb +4 -2
- data/packages/igniter-cluster/lib/igniter/cluster/consensus.rb +18 -0
- data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/governance_contributor.rb +90 -0
- data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/identity_contributor.rb +98 -0
- data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/routing_contributor.rb +674 -0
- data/packages/igniter-cluster/lib/igniter/cluster/diagnostics.rb +24 -0
- data/packages/igniter-cluster/lib/igniter/cluster/events/envelope.rb +136 -0
- data/packages/igniter-cluster/lib/igniter/cluster/events/hook_support.rb +33 -0
- data/packages/igniter-cluster/lib/igniter/cluster/events/log.rb +102 -0
- data/packages/igniter-cluster/lib/igniter/cluster/events/projection_feed.rb +98 -0
- data/packages/igniter-cluster/lib/igniter/cluster/events/read_model_projector.rb +32 -0
- data/packages/igniter-cluster/lib/igniter/cluster/events.rb +131 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_decision.rb +41 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_policy.rb +66 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_queue.rb +88 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_request.rb +62 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_workflow.rb +214 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/checkpoint.rb +141 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/compaction_record.rb +33 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/stores/checkpoint_store.rb +89 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/stores/file_store.rb +249 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance/trail.rb +164 -0
- data/packages/igniter-cluster/lib/igniter/cluster/governance.rb +12 -0
- data/packages/igniter-cluster/lib/igniter/cluster/identity/capability_attestation.rb +114 -0
- data/packages/igniter-cluster/lib/igniter/cluster/identity/manifest.rb +139 -0
- data/packages/igniter-cluster/lib/igniter/cluster/identity/node_identity.rb +106 -0
- data/packages/igniter-cluster/lib/igniter/cluster/identity.rb +5 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/announcer.rb +37 -4
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/checkpoint_gossip.rb +60 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/config.rb +146 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/discovery.rb +7 -2
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/errors.rb +10 -5
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/gossip.rb +19 -4
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/mesh_ql.rb +470 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/node_observation.rb +281 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/observation_query.rb +284 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer.rb +51 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_capacity_report.rb +42 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_identity_envelope.rb +158 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_metadata.rb +122 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_registry.rb +81 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_decision.rb +64 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_planner.rb +154 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_policy.rb +103 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/poller.rb +19 -4
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/rebalance_plan.rb +66 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/rebalance_planner.rb +153 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/repair_loop.rb +169 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/router.rb +306 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/workload_signal.rb +46 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh/workload_tracker.rb +215 -0
- data/packages/igniter-cluster/lib/igniter/cluster/mesh.rb +452 -0
- data/packages/igniter-cluster/lib/igniter/cluster/ownership/claim.rb +69 -0
- data/packages/igniter-cluster/lib/igniter/cluster/ownership/errors.rb +19 -0
- data/packages/igniter-cluster/lib/igniter/cluster/ownership/owner_client.rb +76 -0
- data/packages/igniter-cluster/lib/igniter/cluster/ownership/registry.rb +98 -0
- data/packages/igniter-cluster/lib/igniter/cluster/ownership/resolver.rb +62 -0
- data/packages/igniter-cluster/lib/igniter/cluster/ownership.rb +81 -0
- data/packages/igniter-cluster/lib/igniter/cluster/projection_store.rb +62 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/chunk.rb +49 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/fanout_retriever.rb +93 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/knowledge_shard.rb +140 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/net_http_adapter.rb +85 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/ranker.rb +46 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/retrieval_query.rb +30 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag/retrieval_result.rb +77 -0
- data/packages/igniter-cluster/lib/igniter/cluster/rag.rb +38 -0
- data/packages/igniter-cluster/lib/igniter/cluster/remote_adapter.rb +101 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrapper.rb +3 -1
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrappers/gem.rb +11 -4
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrappers/git.rb +9 -2
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrappers/tarball.rb +8 -2
- data/packages/igniter-cluster/lib/igniter/cluster/replication/capability_query.rb +675 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/expansion_plan.rb +5 -3
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/expansion_planner.rb +41 -29
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/manifest.rb +3 -1
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/network_topology.rb +44 -17
- data/packages/igniter-cluster/lib/igniter/cluster/replication/node_profile.rb +134 -0
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/reflective_replication_agent.rb +50 -29
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/replication_agent.rb +4 -2
- data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/ssh_session.rb +3 -1
- data/packages/igniter-cluster/lib/igniter/cluster/replication.rb +38 -0
- data/packages/igniter-cluster/lib/igniter/cluster/routed_agent_adapter.rb +79 -0
- data/packages/igniter-cluster/lib/igniter/cluster/routing_plan_executor.rb +427 -0
- data/packages/igniter-cluster/lib/igniter/cluster/routing_plan_result.rb +38 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_plan.rb +34 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_planner.rb +76 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_result.rb +34 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_runner.rb +125 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/trust_assessment.rb +37 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/trust_store.rb +58 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust/verifier.rb +80 -0
- data/packages/igniter-cluster/lib/igniter/cluster/trust.rb +9 -0
- data/packages/igniter-cluster/lib/igniter/cluster.rb +71 -0
- data/packages/igniter-cluster/lib/igniter-cluster.rb +3 -0
- data/packages/igniter-core/README.md +21 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/capabilities.rb +3 -1
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/compiled_graph.rb +40 -2
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validation_pipeline.rb +1 -0
- data/packages/igniter-core/lib/igniter/core/compiler/validators/agent_validator.rb +142 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/dependencies_validator.rb +40 -1
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler.rb +1 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/contract.rb +76 -6
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow.rb +7 -7
- data/packages/igniter-core/lib/igniter/core/diagnostics/agent_contributor.rb +241 -0
- data/packages/igniter-core/lib/igniter/core/diagnostics/capability_contributor.rb +162 -0
- data/packages/igniter-core/lib/igniter/core/diagnostics/orchestration_contributor.rb +75 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/report.rb +81 -6
- data/packages/igniter-core/lib/igniter/core/diagnostics.rb +58 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl/contract_builder.rb +163 -6
- data/packages/igniter-core/lib/igniter/core/dto/record.rb +189 -0
- data/packages/igniter-core/lib/igniter/core/dto.rb +8 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/effect.rb +4 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/errors.rb +26 -3
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection/graph_formatter.rb +32 -1
- data/packages/igniter-core/lib/igniter/core/extensions/introspection/plan_formatter.rb +85 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection/runtime_formatter.rb +26 -0
- data/packages/igniter-core/lib/igniter/core/extensions/invariants.rb +70 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental.rb +4 -4
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/memorable.rb +1 -1
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/stores/sqlite.rb +5 -3
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory.rb +11 -11
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics/prometheus_exporter.rb +1 -1
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics.rb +8 -8
- data/packages/igniter-core/lib/igniter/core/model/agent_interaction_contract.rb +172 -0
- data/packages/igniter-core/lib/igniter/core/model/agent_node.rb +86 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/branch_node.rb +37 -1
- data/packages/igniter-core/lib/igniter/core/model/remote_node.rb +91 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model.rb +2 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/node_cache.rb +1 -1
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing.rb +8 -8
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/builder.rb +30 -1
- data/packages/igniter-core/lib/igniter/core/runtime/agent_adapter.rb +41 -0
- data/packages/igniter-core/lib/igniter/core/runtime/agent_result_contract.rb +91 -0
- data/packages/igniter-core/lib/igniter/core/runtime/agent_route.rb +60 -0
- data/packages/igniter-core/lib/igniter/core/runtime/agent_route_resolver.rb +26 -0
- data/packages/igniter-core/lib/igniter/core/runtime/agent_session.rb +922 -0
- data/packages/igniter-core/lib/igniter/core/runtime/agent_session_query.rb +379 -0
- data/packages/igniter-core/lib/igniter/core/runtime/agent_transport.rb +30 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/cache.rb +6 -3
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/deferred_result.rb +27 -1
- data/packages/igniter-core/lib/igniter/core/runtime/execution.rb +913 -0
- data/packages/igniter-core/lib/igniter/core/runtime/job_worker.rb +39 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/node_state.rb +4 -2
- data/packages/igniter-core/lib/igniter/core/runtime/orchestration_overview.rb +213 -0
- data/packages/igniter-core/lib/igniter/core/runtime/orchestration_runtime_state.rb +176 -0
- data/packages/igniter-core/lib/igniter/core/runtime/orchestration_transition_query.rb +208 -0
- data/packages/igniter-core/lib/igniter/core/runtime/planner.rb +301 -0
- data/packages/igniter-core/lib/igniter/core/runtime/proxy_agent_adapter.rb +124 -0
- data/packages/igniter-core/lib/igniter/core/runtime/remote_adapter.rb +26 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/resolver.rb +250 -57
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/result.rb +2 -0
- data/packages/igniter-core/lib/igniter/core/runtime/stores/sqlite_store.rb +155 -0
- data/packages/igniter-core/lib/igniter/core/runtime/stream_result.rb +171 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime.rb +15 -0
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/temporal.rb +1 -1
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/tool/discoverable.rb +3 -3
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/tool.rb +6 -2
- data/{lib/igniter → packages/igniter-core/lib/igniter/core}/version.rb +1 -1
- data/packages/igniter-core/lib/igniter/core.rb +23 -0
- data/packages/igniter-core/lib/igniter-core.rb +3 -0
- data/packages/igniter-extensions/README.md +21 -0
- data/packages/igniter-extensions/lib/igniter/extensions/auditing.rb +3 -0
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/capabilities.rb +1 -1
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/content_addressing.rb +1 -1
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/dataflow.rb +1 -1
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/differential.rb +1 -1
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/execution_report.rb +1 -1
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/incremental.rb +1 -1
- data/packages/igniter-extensions/lib/igniter/extensions/introspection.rb +3 -0
- data/packages/igniter-extensions/lib/igniter/extensions/invariants.rb +3 -0
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/provenance.rb +1 -1
- data/packages/igniter-extensions/lib/igniter/extensions/reactive.rb +3 -0
- data/{lib → packages/igniter-extensions/lib}/igniter/extensions/saga.rb +1 -1
- data/packages/igniter-extensions/lib/igniter/extensions.rb +8 -0
- data/packages/igniter-extensions/lib/igniter-extensions.rb +3 -0
- data/packages/igniter-frontend/README.md +224 -0
- data/packages/igniter-frontend/lib/igniter/frontend/app.rb +90 -0
- data/packages/igniter-frontend/lib/igniter/frontend/app_access.rb +36 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/component.rb +120 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/action_group.rb +53 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/badge.rb +91 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/boolean.rb +53 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/breadcrumbs.rb +71 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/card.rb +114 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/conversation_panel.rb +61 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/datetime.rb +42 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/display_value_support.rb +38 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/empty_state.rb +39 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/event_list.rb +44 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/filters.rb +183 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/indicator.rb +59 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/json_panel.rb +36 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/key_value_list.rb +40 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/loading_state.rb +43 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/metric_grid.rb +37 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/number.rb +53 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/page_header.rb +53 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/pagination.rb +143 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/panel.rb +67 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/percentage.rb +79 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/resource_list.rb +38 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/scenario_card.rb +48 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/shell_columns.rb +67 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/sidebar_shell.rb +106 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/table_with.rb +203 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/tabs.rb +147 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/viz.rb +185 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/page.rb +74 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/raw_text_node.rb +40 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre/template_page.rb +243 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre.rb +48 -0
- data/packages/igniter-frontend/lib/igniter/frontend/arbre_page.rb +7 -0
- data/packages/igniter-frontend/lib/igniter/frontend/assets.rb +101 -0
- data/packages/igniter-frontend/lib/igniter/frontend/builder.rb +124 -0
- data/packages/igniter-frontend/lib/igniter/frontend/component.rb +24 -0
- data/packages/igniter-frontend/lib/igniter/frontend/components.rb +7 -0
- data/packages/igniter-frontend/lib/igniter/frontend/context.rb +53 -0
- data/packages/igniter-frontend/lib/igniter/frontend/form_builder.rb +63 -0
- data/packages/igniter-frontend/lib/igniter/frontend/handler.rb +92 -0
- data/packages/igniter-frontend/lib/igniter/frontend/javascript.rb +353 -0
- data/packages/igniter-frontend/lib/igniter/frontend/page.rb +24 -0
- data/packages/igniter-frontend/lib/igniter/frontend/request.rb +61 -0
- data/packages/igniter-frontend/lib/igniter/frontend/response.rb +67 -0
- data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime/adapters.rb +226 -0
- data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime/presets.rb +147 -0
- data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime.rb +259 -0
- data/packages/igniter-frontend/lib/igniter/frontend/tailwind/surfaces.rb +1074 -0
- data/packages/igniter-frontend/lib/igniter/frontend/tailwind/ui.rb +1438 -0
- data/packages/igniter-frontend/lib/igniter/frontend/tailwind.rb +180 -0
- data/packages/igniter-frontend/lib/igniter/frontend/version.rb +9 -0
- data/packages/igniter-frontend/lib/igniter/frontend.rb +35 -0
- data/packages/igniter-frontend/lib/igniter-frontend.rb +3 -0
- data/packages/igniter-rails/README.md +96 -0
- data/packages/igniter-rails/lib/igniter/plugins/rails/generators/contract/templates/contract.rb.tt +22 -0
- data/packages/igniter-rails/lib/igniter/plugins/rails/generators/install/templates/igniter.rb.tt +16 -0
- data/packages/igniter-rails/lib/igniter-rails.rb +3 -0
- data/packages/igniter-schema-rendering/README.md +27 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/page.rb +35 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/patcher.rb +47 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/renderer.rb +268 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/schema.rb +172 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/store.rb +53 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_normalizer.rb +117 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_processor.rb +91 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_validator.rb +62 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/version.rb +9 -0
- data/packages/igniter-schema-rendering/lib/igniter/schema_rendering.rb +20 -0
- data/packages/igniter-schema-rendering/lib/igniter-schema-rendering.rb +3 -0
- data/packages/igniter-sdk/README.md +25 -0
- data/packages/igniter-sdk/lib/igniter/sdk/channels/base.rb +84 -0
- data/packages/igniter-sdk/lib/igniter/sdk/channels/delivery_result.rb +61 -0
- data/packages/igniter-sdk/lib/igniter/sdk/channels/message.rb +101 -0
- data/packages/igniter-sdk/lib/igniter/sdk/channels/telegram.rb +161 -0
- data/packages/igniter-sdk/lib/igniter/sdk/channels/webhook.rb +213 -0
- data/packages/igniter-sdk/lib/igniter/sdk/channels.rb +17 -0
- data/packages/igniter-sdk/lib/igniter/sdk/data/store.rb +31 -0
- data/packages/igniter-sdk/lib/igniter/sdk/data/stores/file.rb +113 -0
- data/packages/igniter-sdk/lib/igniter/sdk/data/stores/in_memory.rb +63 -0
- data/packages/igniter-sdk/lib/igniter/sdk/data/stores/sqlite.rb +144 -0
- data/packages/igniter-sdk/lib/igniter/sdk/data.rb +34 -0
- data/packages/igniter-sdk/lib/igniter/sdk/tools/agent_bootstrap_tool.rb +151 -0
- data/packages/igniter-sdk/lib/igniter/sdk/tools/local_workflow_selector_tool.rb +269 -0
- data/packages/igniter-sdk/lib/igniter/sdk/tools/system_discovery_tool.rb +198 -0
- data/packages/igniter-sdk/lib/igniter/sdk/tools.rb +9 -0
- data/packages/igniter-sdk/lib/igniter/sdk.rb +86 -0
- data/packages/igniter-sdk/lib/igniter-sdk.rb +3 -0
- data/packages/igniter-server/README.md +9 -0
- data/packages/igniter-server/lib/igniter/server/agent_session_store.rb +98 -0
- data/packages/igniter-server/lib/igniter/server/agent_transport.rb +95 -0
- data/packages/igniter-server/lib/igniter/server/app_host.rb +3 -0
- data/{lib → packages/igniter-server/lib}/igniter/server/client.rb +96 -6
- data/packages/igniter-server/lib/igniter/server/config.rb +70 -0
- data/packages/igniter-server/lib/igniter/server/handlers/agent_message_handler.rb +107 -0
- data/packages/igniter-server/lib/igniter/server/handlers/agent_session_handler.rb +125 -0
- data/packages/igniter-server/lib/igniter/server/handlers/manifest_handler.rb +77 -0
- data/{lib → packages/igniter-server/lib}/igniter/server/handlers/metrics_handler.rb +1 -1
- data/{lib → packages/igniter-server/lib}/igniter/server/handlers/peers_handler.rb +38 -17
- data/{lib → packages/igniter-server/lib}/igniter/server/http_server.rb +91 -15
- data/{lib → packages/igniter-server/lib}/igniter/server/rack_app.rb +27 -2
- data/packages/igniter-server/lib/igniter/server/remote_adapter.rb +27 -0
- data/packages/igniter-server/lib/igniter/server/router.rb +291 -0
- data/{lib → packages/igniter-server/lib}/igniter/server/server_logger.rb +3 -1
- data/{lib → packages/igniter-server/lib}/igniter/server.rb +58 -1
- data/packages/igniter-server/lib/igniter-server.rb +3 -0
- metadata +631 -282
- data/docs/API_V2.md +0 -537
- data/docs/APPLICATION_V1.md +0 -253
- data/docs/ARCHITECTURE_V2.md +0 -317
- data/docs/BACKLOG.md +0 -166
- data/docs/BRANCHES_V1.md +0 -213
- data/docs/CAPABILITIES_V1.md +0 -207
- data/docs/COLLECTIONS_V1.md +0 -303
- data/docs/CONSENSUS_V1.md +0 -477
- data/docs/CONTENT_ADDRESSING_V1.md +0 -221
- data/docs/DATAFLOW_V1.md +0 -274
- data/docs/DISTRIBUTED_CONTRACTS_V1.md +0 -493
- data/docs/EXECUTION_MODEL_V2.md +0 -324
- data/docs/IGNITER_CONCEPTS.md +0 -81
- data/docs/LLM_V1.md +0 -335
- data/docs/MESH_V1.md +0 -732
- data/docs/NODE_CACHE_V1.md +0 -324
- data/docs/PATTERNS.md +0 -411
- data/docs/PROACTIVE_AGENTS_V1.md +0 -293
- data/docs/SERVER_V1.md +0 -512
- data/docs/SKILLS_V1.md +0 -213
- data/docs/STORE_ADAPTERS.md +0 -154
- data/docs/TEMPORAL_V1.md +0 -174
- data/docs/TOOLS_V1.md +0 -347
- data/docs/TRANSCRIPTION_V1.md +0 -403
- data/lib/igniter/agents.rb +0 -56
- data/lib/igniter/application/app_config.rb +0 -32
- data/lib/igniter/application/generator.rb +0 -157
- data/lib/igniter/application/yml_loader.rb +0 -39
- data/lib/igniter/application.rb +0 -174
- data/lib/igniter/consensus.rb +0 -58
- data/lib/igniter/diagnostics.rb +0 -8
- data/lib/igniter/extensions/introspection/plan_formatter.rb +0 -55
- data/lib/igniter/extensions/invariants.rb +0 -116
- data/lib/igniter/extensions/mesh.rb +0 -31
- data/lib/igniter/integrations/agents.rb +0 -18
- data/lib/igniter/mesh/config.rb +0 -45
- data/lib/igniter/mesh/peer.rb +0 -21
- data/lib/igniter/mesh/peer_registry.rb +0 -51
- data/lib/igniter/mesh/router.rb +0 -109
- data/lib/igniter/mesh.rb +0 -85
- data/lib/igniter/model/remote_node.rb +0 -42
- data/lib/igniter/replication/node_role.rb +0 -42
- data/lib/igniter/replication/role_registry.rb +0 -73
- data/lib/igniter/replication.rb +0 -54
- data/lib/igniter/runtime/execution.rb +0 -416
- data/lib/igniter/runtime/job_worker.rb +0 -18
- data/lib/igniter/runtime/planner.rb +0 -126
- data/lib/igniter/server/config.rb +0 -34
- data/lib/igniter/server/handlers/manifest_handler.rb +0 -34
- data/lib/igniter/server/router.rb +0 -108
- data/lib/igniter/skill.rb +0 -218
- data/lib/igniter/tool_registry.rb +0 -144
- /data/{lib → packages/igniter-agents/lib}/igniter/agent/mailbox.rb +0 -0
- /data/{lib → packages/igniter-agents/lib}/igniter/agent/message.rb +0 -0
- /data/{lib → packages/igniter-agents/lib}/igniter/agent/state_holder.rb +0 -0
- /data/{lib → packages/igniter-agents/lib}/igniter/agents/observability/metrics_agent.rb +0 -0
- /data/{lib → packages/igniter-agents/lib}/igniter/agents/pipeline/batch_processor_agent.rb +0 -0
- /data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/agents/proactive}/alert_agent.rb +0 -0
- /data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/agents/proactive}/health_check_agent.rb +0 -0
- /data/{lib → packages/igniter-agents/lib}/igniter/agents/reliability/retry_agent.rb +0 -0
- /data/{lib → packages/igniter-agents/lib}/igniter/agents/scheduling/cron_agent.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/graph_compiler.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/type_resolver.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validation_context.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/await_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/callable_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/outputs_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/remote_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/type_compatibility_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/uniqueness_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/content_addressing.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/aggregate_operators.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/aggregate_state.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/diff.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/diff_state.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/incremental_collection_result.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/window_filter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/auditing/report/console_formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/auditing/report/markdown_formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/introspection/formatters/mermaid_formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/introspection/formatters/text_tree_formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/divergence.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/report.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/runner.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl/schema_builder.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/effect_registry.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events/bus.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events/event.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/builder.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/node_entry.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/report.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/executor.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/executor_registry.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/auditing/timeline.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/auditing.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/engine.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/matcher.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/reaction.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/fingerprint.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/result.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/tracker.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/invariant.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/agent_memory.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/episode.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/fact.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/reflection_cycle.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/reflection_record.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/store.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/stores/in_memory.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics/collector.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics/snapshot.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/aggregate_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/await_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/collection_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/composition_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/compute_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/effect_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/graph.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/input_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/output_node.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/generators.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/result.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/run.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/runner.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/lineage.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/node_trace.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/text_formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/collection_result.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/input_validator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/invalidator.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runner_factory.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/inline_runner.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/store_runner.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/thread_pool_runner.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/active_record_store.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/file_store.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/memory_store.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/redis_store.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/compensation.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/compensation_record.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/executor.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/formatter.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/result.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/stream_loop.rb +0 -0
- /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/type_system.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/cable_adapter.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/contract_job.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/generators/contract/contract_generator.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/generators/install/install_generator.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/railtie.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/webhook_concern.rb +0 -0
- /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/base.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/contracts_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/event_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/execute_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/health_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/liveness_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/readiness_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/status_handler.rb +0 -0
- /data/{lib → packages/igniter-server/lib}/igniter/server/registry.rb +0 -0
data/docs/CONSENSUS_V1.md
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
# Distributed Consensus with Igniter — v1
|
|
2
|
-
|
|
3
|
-
`require "igniter/consensus"` provides a Raft-inspired consensus cluster built on
|
|
4
|
-
Igniter's Actor primitives. The Raft protocol is fully encapsulated — users interact
|
|
5
|
-
with the high-level `Cluster` API and an optional `StateMachine` subclass.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Quick start
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
require "igniter/consensus"
|
|
13
|
-
|
|
14
|
-
# Start a 5-node cluster with the built-in key-value state machine
|
|
15
|
-
cluster = Igniter::Consensus::Cluster.start(nodes: %i[n1 n2 n3 n4 n5])
|
|
16
|
-
cluster.wait_for_leader
|
|
17
|
-
|
|
18
|
-
cluster.write(key: :price, value: 99) # replicated to all nodes
|
|
19
|
-
cluster.read(:price) # => 99
|
|
20
|
-
|
|
21
|
-
cluster.stop!
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Architecture
|
|
27
|
-
|
|
28
|
-
Two complementary Igniter primitives map naturally to consensus protocols:
|
|
29
|
-
|
|
30
|
-
| Primitive | Role |
|
|
31
|
-
|-----------|------|
|
|
32
|
-
| `Igniter::Consensus::Node` | Raft agent — leader election, log replication (internal) |
|
|
33
|
-
| `Igniter::Consensus::Cluster` | Lifecycle management + high-level read/write API |
|
|
34
|
-
| `Igniter::Consensus::StateMachine` | User-extensible state machine DSL |
|
|
35
|
-
| `Igniter::Consensus::ReadQuery` | Built-in single-shot read Contract |
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
Cluster of 5 Node agents, each registered in Igniter::Registry
|
|
39
|
-
|
|
40
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
41
|
-
│ n1: follower │ n2: follower │ n3: LEADER │ n4: follower │ n5: follower
|
|
42
|
-
└───────────────┴───────────────┴──────────────┴───────────────┴───────────────┘
|
|
43
|
-
│
|
|
44
|
-
┌──────────────────┼──────────────────┐
|
|
45
|
-
heartbeat (50ms) AppendEntries commit on quorum(3/5)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Leader election flow
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
Follower ──(timeout 1–1.5 s)──► Candidate ──(quorum votes)──► Leader
|
|
52
|
-
▲ │
|
|
53
|
-
└────────────────── AppendEntries heartbeat (50 ms) ◄──────────┘
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
1. A follower that receives no heartbeat within its randomised election timeout
|
|
57
|
-
becomes a **Candidate** and broadcasts `RequestVote` to all peers.
|
|
58
|
-
2. A node that hasn't voted in this term grants its vote if the candidate's log
|
|
59
|
-
is at least as up-to-date as its own.
|
|
60
|
-
3. The first candidate to collect **majority votes** (quorum = ⌊N/2⌋ + 1) becomes
|
|
61
|
-
**Leader** and immediately starts heartbeating.
|
|
62
|
-
4. Randomised timeouts (1.0–1.5 s) prevent simultaneous elections (split votes).
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## `Cluster` API
|
|
67
|
-
|
|
68
|
-
### Starting a cluster
|
|
69
|
-
|
|
70
|
-
```ruby
|
|
71
|
-
cluster = Igniter::Consensus::Cluster.start(
|
|
72
|
-
nodes: %i[n1 n2 n3 n4 n5], # Registry names for each node
|
|
73
|
-
state_machine: MyStateMachine, # optional — default is KV store
|
|
74
|
-
verbose: false, # print Raft events to stdout
|
|
75
|
-
)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
`start` creates nodes and returns immediately — it does **not** wait for leader
|
|
79
|
-
election. Call `wait_for_leader` if you need a leader before proceeding.
|
|
80
|
-
|
|
81
|
-
### Waiting for a leader
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
leader_ref = cluster.wait_for_leader # blocks up to ~2 s
|
|
85
|
-
leader_ref = cluster.wait_for_leader(timeout: 5) # custom timeout
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Raises `Igniter::Consensus::NoLeaderError` if no leader is elected within the timeout.
|
|
89
|
-
|
|
90
|
-
### Writing
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
# Default KV protocol
|
|
94
|
-
cluster.write(key: :price, value: 99)
|
|
95
|
-
|
|
96
|
-
# Custom state machine command
|
|
97
|
-
cluster.write(type: :add_order, id: "o1", data: { price: 42, qty: 10 })
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Raises `NoLeaderError` if no leader is available. Returns `self` (chainable).
|
|
101
|
-
|
|
102
|
-
### Reading
|
|
103
|
-
|
|
104
|
-
```ruby
|
|
105
|
-
cluster.read(:price) # => 99 (reads from leader's committed state)
|
|
106
|
-
cluster.state_machine_snapshot # => { price: 99, ... } (full snapshot)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Quorum and status
|
|
110
|
-
|
|
111
|
-
```ruby
|
|
112
|
-
cluster.quorum_size # => 3 (minimum votes for 5-node cluster)
|
|
113
|
-
cluster.has_quorum? # => true
|
|
114
|
-
cluster.alive_count # => 5
|
|
115
|
-
cluster.status # => [{ node_id: :n1, role: :follower, term: 2, ... }, ...]
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Stopping
|
|
119
|
-
|
|
120
|
-
```ruby
|
|
121
|
-
cluster.stop! # graceful stop of all nodes (timeout: 2s default)
|
|
122
|
-
cluster.stop!(timeout: 5)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Contract integration
|
|
126
|
-
|
|
127
|
-
```ruby
|
|
128
|
-
q = cluster.read_contract(key: :price) # returns ReadQuery instance
|
|
129
|
-
q.resolve_all
|
|
130
|
-
q.result.value # => 99
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## `StateMachine` — custom command reducers
|
|
136
|
-
|
|
137
|
-
Subclass `Igniter::Consensus::StateMachine` and declare handlers with `apply`:
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
class OrderBook < Igniter::Consensus::StateMachine
|
|
141
|
-
# Each handler receives (state, command) and must return the NEW state (immutably).
|
|
142
|
-
apply :add_order do |state, cmd|
|
|
143
|
-
state.merge(cmd[:id] => cmd[:order])
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
apply :cancel_order do |state, cmd|
|
|
147
|
-
state.reject { |k, _| k == cmd[:id] }
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
apply :update_price do |state, cmd|
|
|
151
|
-
return state unless state.key?(cmd[:id])
|
|
152
|
-
state.merge(cmd[:id] => state[cmd[:id]].merge(price: cmd[:price]))
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
cluster = Igniter::Consensus::Cluster.start(
|
|
157
|
-
nodes: %i[n1 n2 n3 n4 n5],
|
|
158
|
-
state_machine: OrderBook,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
cluster.write(type: :add_order, id: "ord-1", order: { vendor: "ACME", price: 42.0 })
|
|
162
|
-
cluster.read("ord-1") # => { vendor: "ACME", price: 42.0 }
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Default KV protocol (no subclass needed)
|
|
166
|
-
|
|
167
|
-
When no `state_machine:` is provided, commands use a simple key-value protocol:
|
|
168
|
-
|
|
169
|
-
| Command | Effect |
|
|
170
|
-
|---------|--------|
|
|
171
|
-
| `{ key: :x, value: 42 }` | Set `state_machine[:x] = 42` |
|
|
172
|
-
| `{ key: :x, op: :delete }` | Remove `:x` from state machine |
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## `ReadQuery` — declarative Contract read
|
|
177
|
-
|
|
178
|
-
`ReadQuery` is a built-in `Igniter::Contract` with the dependency graph
|
|
179
|
-
`find_leader → read_value`:
|
|
180
|
-
|
|
181
|
-
```ruby
|
|
182
|
-
q = Igniter::Consensus::ReadQuery.new(cluster: cluster, key: :price)
|
|
183
|
-
q.resolve_all
|
|
184
|
-
q.result.value # => 99
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Or use `Cluster#read_contract` for convenience:
|
|
188
|
-
|
|
189
|
-
```ruby
|
|
190
|
-
q = cluster.read_contract(key: :price)
|
|
191
|
-
q.resolve_all
|
|
192
|
-
q.result.value # => 99
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Custom read Contract
|
|
196
|
-
|
|
197
|
-
You can build your own Contracts using the bundled executors:
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
class PriceCheck < Igniter::Contract
|
|
201
|
-
define do
|
|
202
|
-
input :cluster
|
|
203
|
-
input :threshold
|
|
204
|
-
|
|
205
|
-
compute :leader, with: :cluster, call: Igniter::Consensus::FindLeader
|
|
206
|
-
compute :current_price, with: [:leader], call: ReadCurrentPrice
|
|
207
|
-
compute :verdict, with: [:current_price, :threshold], call: EvaluatePrice
|
|
208
|
-
|
|
209
|
-
output :verdict
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## Practical Example — `BidAuction`
|
|
217
|
-
|
|
218
|
-
Models the auction problem: N vendors submit bids durably to the consensus log
|
|
219
|
-
before the winner is selected. Combines `Igniter::Contract` parallel execution
|
|
220
|
-
with consensus-backed durability.
|
|
221
|
-
|
|
222
|
-
```ruby
|
|
223
|
-
class SubmitBid < Igniter::Executor
|
|
224
|
-
# The dep name varies per compute node (vendor1_bid / vendor2_bid / …).
|
|
225
|
-
# Using ** captures whichever named bid dep is passed.
|
|
226
|
-
def call(cluster:, **bid_kwarg)
|
|
227
|
-
bid = bid_kwarg.values.first # { vendor_id:, price: }
|
|
228
|
-
ref = cluster.leader
|
|
229
|
-
raise Igniter::ResolutionError, "No leader — cannot submit bid" unless ref
|
|
230
|
-
ref.send(:client_write, command: { key: :"bid_#{bid[:vendor_id]}", value: bid[:price] })
|
|
231
|
-
bid
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
class SelectWinner < Igniter::Executor
|
|
236
|
-
def call(bid1:, bid2:, bid3:)
|
|
237
|
-
[bid1, bid2, bid3].min_by { |b| b[:price] }
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
class BidAuction < Igniter::Contract
|
|
242
|
-
runner :thread_pool, pool_size: 3 # bid1, bid2, bid3 run concurrently
|
|
243
|
-
|
|
244
|
-
define do
|
|
245
|
-
input :cluster
|
|
246
|
-
input :vendor1_bid # { vendor_id: String, price: Float }
|
|
247
|
-
input :vendor2_bid
|
|
248
|
-
input :vendor3_bid
|
|
249
|
-
|
|
250
|
-
# No deps between bid1/bid2/bid3 → submitted to the consensus log in parallel
|
|
251
|
-
compute :bid1, with: [:cluster, :vendor1_bid], call: SubmitBid
|
|
252
|
-
compute :bid2, with: [:cluster, :vendor2_bid], call: SubmitBid
|
|
253
|
-
compute :bid3, with: [:cluster, :vendor3_bid], call: SubmitBid
|
|
254
|
-
|
|
255
|
-
# Depends on all three → runs only after every bid is committed
|
|
256
|
-
compute :winner, with: [:bid1, :bid2, :bid3], call: SelectWinner
|
|
257
|
-
|
|
258
|
-
output :winner
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Usage:
|
|
264
|
-
|
|
265
|
-
```ruby
|
|
266
|
-
auction = BidAuction.new(
|
|
267
|
-
cluster: cluster,
|
|
268
|
-
vendor1_bid: { vendor_id: "alpha", price: 45.00 },
|
|
269
|
-
vendor2_bid: { vendor_id: "betacor", price: 38.50 },
|
|
270
|
-
vendor3_bid: { vendor_id: "gamma", price: 52.00 },
|
|
271
|
-
)
|
|
272
|
-
auction.resolve_all
|
|
273
|
-
puts auction.result.winner # => { vendor_id: "betacor", price: 38.5 }
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
The Igniter dependency graph enforces the correct ordering automatically:
|
|
277
|
-
- `bid1`, `bid2`, `bid3` have no mutual deps → `thread_pool` submits them concurrently.
|
|
278
|
-
- `winner` depends on all three → it never runs before every bid is logged.
|
|
279
|
-
|
|
280
|
-
---
|
|
281
|
-
|
|
282
|
-
## Quorum Failure and Safety Guarantees
|
|
283
|
-
|
|
284
|
-
Raft is a **CP system** (Consistent + Partition-tolerant). With fewer than `⌊N/2⌋ + 1`
|
|
285
|
-
nodes alive, no leader can be elected and the cluster is **unavailable** — but it
|
|
286
|
-
never returns stale or conflicting data.
|
|
287
|
-
|
|
288
|
-
```ruby
|
|
289
|
-
# Kill enough nodes to break quorum (3/5 needed, only 2 survive)
|
|
290
|
-
(3).times { cluster_nodes.pop.kill }
|
|
291
|
-
|
|
292
|
-
begin
|
|
293
|
-
cluster.read_contract(key: :price).resolve_all
|
|
294
|
-
rescue Igniter::Error => e
|
|
295
|
-
puts e.message
|
|
296
|
-
# => "No leader in cluster — retry later [graph=ReadQuery, node=leader …]"
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# Or catch at the Cluster level before even attempting a Contract:
|
|
300
|
-
cluster.has_quorum? # => false
|
|
301
|
-
cluster.write(key: :x, value: 1)
|
|
302
|
-
# => Igniter::Consensus::NoLeaderError: No leader available
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
`FindLeader` scans all known nodes, finds no leader, and raises `Igniter::ResolutionError`.
|
|
306
|
-
The resolver enriches it with full context (graph, node, execution_id).
|
|
307
|
-
|
|
308
|
-
---
|
|
309
|
-
|
|
310
|
-
## Critical Implementation Gotchas
|
|
311
|
-
|
|
312
|
-
### 1. `next` not `return` inside `on` blocks
|
|
313
|
-
|
|
314
|
-
`on :type do |state:, payload:| … end` registers a **Proc** (not a lambda).
|
|
315
|
-
`return` inside a Proc raises `LocalJumpError` at runtime.
|
|
316
|
-
Use `next value` for early exits:
|
|
317
|
-
|
|
318
|
-
```ruby
|
|
319
|
-
# ✗ LocalJumpError at runtime
|
|
320
|
-
on :vote_response do |state:, payload:|
|
|
321
|
-
return state unless msg[:vote_granted]
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
# ✓ correct
|
|
325
|
-
on :vote_response do |state:, payload:|
|
|
326
|
-
next state unless msg[:vote_granted]
|
|
327
|
-
end
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### 2. Sync-reply handlers must return a non-Hash
|
|
331
|
-
|
|
332
|
-
The Agent runner applies this logic to the handler's return value:
|
|
333
|
-
|
|
334
|
-
```
|
|
335
|
-
Hash → @state_holder.set(result) # treated as NEW STATE; caller gets nil
|
|
336
|
-
other → send as reply to call()
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
For synchronous queries, wrap the result in a Struct:
|
|
340
|
-
|
|
341
|
-
```ruby
|
|
342
|
-
StatusInfo = Struct.new(:role, :term, :node_id, keyword_init: true)
|
|
343
|
-
|
|
344
|
-
on :status do |state:, payload:|
|
|
345
|
-
StatusInfo.new(role: state[:role], term: state[:term], node_id: state[:node_id])
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
ref.call(:status).role # => :leader
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
`Igniter::Consensus::Node` uses `NodeStatus` and `NodeReadResult` structs internally.
|
|
352
|
-
|
|
353
|
-
### 3. `ref.state` vs `ref.call()`
|
|
354
|
-
|
|
355
|
-
| Method | Mechanism | Use when |
|
|
356
|
-
|--------|-----------|----------|
|
|
357
|
-
| `ref.state` | Reads `StateHolder` directly (Mutex, no mailbox) | Polling from the main thread; leader discovery |
|
|
358
|
-
| `ref.call(:type)` | Goes through mailbox, blocks until reply | Need agent-thread consistency; sync queries |
|
|
359
|
-
|
|
360
|
-
`Cluster#leader` and `Cluster#read` use `ref.state` for performance-sensitive
|
|
361
|
-
leader polling.
|
|
362
|
-
|
|
363
|
-
### 4. Class-method helpers must NOT be `private_class_method`
|
|
364
|
-
|
|
365
|
-
Helpers called from `schedule`/`on` blocks (which run in the Agent's Runner thread)
|
|
366
|
-
must be accessible via explicit class reference (`Node.find_peer`, etc.). Making them
|
|
367
|
-
`private_class_method` blocks these calls since the blocks use explicit receiver form.
|
|
368
|
-
|
|
369
|
-
### 5. Heartbeat : election timeout ratio
|
|
370
|
-
|
|
371
|
-
Raft recommends heartbeat interval be **10× smaller** than the minimum election
|
|
372
|
-
timeout. With Ruby's green-thread scheduling jitter, a 1:20 ratio is more reliable:
|
|
373
|
-
|
|
374
|
-
```ruby
|
|
375
|
-
HEARTBEAT_INTERVAL = 0.05 # 50 ms
|
|
376
|
-
ELECTION_TIMEOUT_BASE = 1.0 # 1000 ms minimum (1:20 ratio)
|
|
377
|
-
ELECTION_TIMEOUT_JITTER = 0.5 # + random 0–500 ms
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
With a tighter ratio (e.g., 1:3) followers can time out before they receive the
|
|
381
|
-
first heartbeat from a freshly elected leader, causing cascading elections.
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Extending the Pattern
|
|
386
|
-
|
|
387
|
-
### Custom state machine commands
|
|
388
|
-
|
|
389
|
-
```ruby
|
|
390
|
-
class InventoryMachine < Igniter::Consensus::StateMachine
|
|
391
|
-
apply :set do |state, cmd| state.merge(cmd[:key] => cmd[:value]) end
|
|
392
|
-
apply :incr do |state, cmd| state.merge(cmd[:key] => (state[cmd[:key]] || 0) + cmd[:by]) end
|
|
393
|
-
apply :delete do |state, cmd| state.reject { |k, _| k == cmd[:key] } end
|
|
394
|
-
end
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### Distributed lock
|
|
398
|
-
|
|
399
|
-
```ruby
|
|
400
|
-
class AcquireLock < Igniter::Executor
|
|
401
|
-
def call(cluster:, lock_key:, owner:)
|
|
402
|
-
ref = cluster.leader
|
|
403
|
-
raise Igniter::ResolutionError, "No leader" unless ref
|
|
404
|
-
current = ref.state[:state_machine][lock_key]
|
|
405
|
-
raise Igniter::ResolutionError, "Lock held by #{current}" if current
|
|
406
|
-
ref.send(:client_write, command: { key: lock_key, value: owner })
|
|
407
|
-
:acquired
|
|
408
|
-
end
|
|
409
|
-
end
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### Multi-Raft / partitioned keyspace
|
|
413
|
-
|
|
414
|
-
Start separate clusters per shard and route writes by key hash:
|
|
415
|
-
|
|
416
|
-
```ruby
|
|
417
|
-
SHARD_CLUSTERS = {
|
|
418
|
-
0 => Igniter::Consensus::Cluster.start(nodes: %i[n1a n2a n3a]),
|
|
419
|
-
1 => Igniter::Consensus::Cluster.start(nodes: %i[n1b n2b n3b]),
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
def shard_for(key) = key.hash % SHARD_CLUSTERS.size
|
|
423
|
-
def cluster_for(key) = SHARD_CLUSTERS[shard_for(key)]
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### Redis-backed log persistence
|
|
427
|
-
|
|
428
|
-
Access the underlying Node agent via `Igniter::Registry` to intercept writes:
|
|
429
|
-
|
|
430
|
-
```ruby
|
|
431
|
-
# Subscribe to writes via a custom state machine that persists to Redis:
|
|
432
|
-
class RedisBackedMachine < Igniter::Consensus::StateMachine
|
|
433
|
-
apply :set do |state, cmd|
|
|
434
|
-
$redis.rpush("raft:log", { key: cmd[:key], value: cmd[:value] }.to_json)
|
|
435
|
-
state.merge(cmd[:key] => cmd[:value])
|
|
436
|
-
end
|
|
437
|
-
end
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## Running the Demo
|
|
443
|
-
|
|
444
|
-
```bash
|
|
445
|
-
bundle exec ruby examples/consensus.rb
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
Output covers 10 steps:
|
|
449
|
-
|
|
450
|
-
| Step | Description |
|
|
451
|
-
|------|-------------|
|
|
452
|
-
| 1 | Start 5-node cluster |
|
|
453
|
-
| 2 | Leader elected via `wait_for_leader` |
|
|
454
|
-
| 3 | Two writes committed to the log |
|
|
455
|
-
| 4 | Full cluster status snapshot |
|
|
456
|
-
| 5 | `ReadQuery` contract reads `:price` |
|
|
457
|
-
| 6 | Leader crash simulation |
|
|
458
|
-
| 7 | New leader elected; write + read after failover |
|
|
459
|
-
| 8 | Custom `CounterMachine` in a 3-node cluster |
|
|
460
|
-
| 9 | `BidAuction` — three vendors bid in parallel; bids replicated before winner selected |
|
|
461
|
-
| 10 | Quorum failure — `ReadQuery` raises `ResolutionError` (CP guarantee) |
|
|
462
|
-
|
|
463
|
-
---
|
|
464
|
-
|
|
465
|
-
## Files
|
|
466
|
-
|
|
467
|
-
| File | Purpose |
|
|
468
|
-
|------|---------|
|
|
469
|
-
| `lib/igniter/consensus.rb` | Entry point (`require "igniter/consensus"`) |
|
|
470
|
-
| `lib/igniter/consensus/cluster.rb` | **Public API** — lifecycle, read, write, status |
|
|
471
|
-
| `lib/igniter/consensus/state_machine.rb` | User DSL — `apply :type do \|state, cmd\| end` |
|
|
472
|
-
| `lib/igniter/consensus/node.rb` | Internal Raft agent (full protocol) |
|
|
473
|
-
| `lib/igniter/consensus/executors.rb` | `FindLeader`, `ReadValue`, `SubmitCommand` |
|
|
474
|
-
| `lib/igniter/consensus/read_query.rb` | `ReadQuery` built-in Contract |
|
|
475
|
-
| `lib/igniter/consensus/errors.rb` | `NoLeaderError`, `QuorumLostError` |
|
|
476
|
-
| `examples/consensus.rb` | Full working demo (10 steps) |
|
|
477
|
-
| `spec/igniter/consensus_spec.rb` | Test suite (35 examples) |
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
# Content-Addressed Computation — v1
|
|
2
|
-
|
|
3
|
-
Content addressing gives `pure` executors a universal cache key derived from their logic
|
|
4
|
-
(fingerprint) and their input values. The same computation — regardless of which
|
|
5
|
-
contract, which execution, or which process produced it — always returns the cached result.
|
|
6
|
-
|
|
7
|
-
This is the Nix/Merkle model applied to contract nodes: **identical inputs → identical
|
|
8
|
-
output, fetched from cache**.
|
|
9
|
-
|
|
10
|
-
## Quick Start
|
|
11
|
-
|
|
12
|
-
```ruby
|
|
13
|
-
require "igniter/extensions/content_addressing"
|
|
14
|
-
|
|
15
|
-
class TaxCalculator < Igniter::Executor
|
|
16
|
-
pure # marks executor as side-effect-free
|
|
17
|
-
fingerprint "tax_calc_v1" # optional: bumps the cache key when logic changes
|
|
18
|
-
|
|
19
|
-
def call(country:, amount:)
|
|
20
|
-
TAX_RATES[country] * amount
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
class InvoiceContract < Igniter::Contract
|
|
25
|
-
define do
|
|
26
|
-
input :country
|
|
27
|
-
input :amount, type: :numeric
|
|
28
|
-
|
|
29
|
-
compute :tax, depends_on: %i[country amount], call: TaxCalculator
|
|
30
|
-
|
|
31
|
-
output :tax
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# First execution — computes and caches the result
|
|
36
|
-
c1 = InvoiceContract.new(country: "UA", amount: 1000)
|
|
37
|
-
c1.result.tax # => 220.0 (computed)
|
|
38
|
-
|
|
39
|
-
# Second execution with identical inputs — served from the content cache
|
|
40
|
-
c2 = InvoiceContract.new(country: "UA", amount: 1000)
|
|
41
|
-
c2.result.tax # => 220.0 (cache hit — TaxCalculator was never called)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## How It Works
|
|
45
|
-
|
|
46
|
-
For every `pure` executor, the resolver computes a **content key** before calling
|
|
47
|
-
the executor:
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
key = SHA-256( fingerprint + "\x00" + stable_serialize(dep_values) )[0..23]
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
- **fingerprint** — the executor class name (or explicit `fingerprint "v1"` string).
|
|
54
|
-
- **stable_serialize** — deterministic, order-independent serialization of all dependency
|
|
55
|
-
values: Hash keys are sorted, Array elements are serialized recursively, primitives use
|
|
56
|
-
`inspect`.
|
|
57
|
-
|
|
58
|
-
The resolver looks up the key in the global `ContentAddressing.cache` before calling the
|
|
59
|
-
executor. On a hit it uses the cached value and emits a `:node_content_cache_hit` event.
|
|
60
|
-
On a miss it computes the result, stores it, and continues normally.
|
|
61
|
-
|
|
62
|
-
## Executor DSL
|
|
63
|
-
|
|
64
|
-
### `pure`
|
|
65
|
-
|
|
66
|
-
Marks the executor as having no side effects. Enables content-addressed caching.
|
|
67
|
-
Shorthand for `capabilities(:pure)`.
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
class MyExecutor < Igniter::Executor
|
|
71
|
-
pure
|
|
72
|
-
end
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### `fingerprint "v1"`
|
|
76
|
-
|
|
77
|
-
Sets an explicit version string used as the first component of the content key.
|
|
78
|
-
Bump the fingerprint whenever the executor logic changes to immediately invalidate
|
|
79
|
-
all cached results for this executor.
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
class TaxCalculator < Igniter::Executor
|
|
83
|
-
pure
|
|
84
|
-
fingerprint "tax_calc_v2" # bumped — old v1 cache entries are ignored
|
|
85
|
-
end
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
If `fingerprint` is not set, the executor's class name is used. Anonymous executors
|
|
89
|
-
use `"anonymous_executor"`.
|
|
90
|
-
|
|
91
|
-
## Content Key
|
|
92
|
-
|
|
93
|
-
`Igniter::ContentAddressing::ContentKey` is an immutable value object:
|
|
94
|
-
|
|
95
|
-
```ruby
|
|
96
|
-
key = Igniter::ContentAddressing::ContentKey.compute(TaxCalculator, { country: "UA", amount: 1000 })
|
|
97
|
-
|
|
98
|
-
key.hex # => "8f47805dc6dd7926" (24-hex digest prefix)
|
|
99
|
-
key.to_s # => "ca:8f47805dc6dd7926"
|
|
100
|
-
key == key # => true (equality by hex, not object identity)
|
|
101
|
-
key.frozen? # => true
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Keys are equal if their hex values match — two keys computed from the same executor and
|
|
105
|
-
the same dep values will always be equal, even if produced in separate processes.
|
|
106
|
-
|
|
107
|
-
## Content Cache
|
|
108
|
-
|
|
109
|
-
The global cache is a thread-safe in-process Hash by default:
|
|
110
|
-
|
|
111
|
-
```ruby
|
|
112
|
-
Igniter::ContentAddressing.cache # => #<Igniter::ContentAddressing::Cache ...>
|
|
113
|
-
Igniter::ContentAddressing.cache.stats # => { size: 3, hits: 12, misses: 4 }
|
|
114
|
-
Igniter::ContentAddressing.cache.size # => 3
|
|
115
|
-
Igniter::ContentAddressing.cache.clear # clears entries and resets counters
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Distributed Cache (Redis)
|
|
119
|
-
|
|
120
|
-
Replace the default cache with any object implementing `#fetch(key)` and `#store(key, value)`:
|
|
121
|
-
|
|
122
|
-
```ruby
|
|
123
|
-
class RedisContentCache
|
|
124
|
-
def initialize(redis, ttl: 3600)
|
|
125
|
-
@redis = redis
|
|
126
|
-
@ttl = ttl
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def fetch(key)
|
|
130
|
-
val = @redis.get(key.to_s)
|
|
131
|
-
val ? Marshal.load(val) : nil
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def store(key, value)
|
|
135
|
-
@redis.setex(key.to_s, @ttl, Marshal.dump(value))
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
Igniter::ContentAddressing.cache = RedisContentCache.new(Redis.new)
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
With a shared Redis cache, results are reused across deployments, canary instances,
|
|
143
|
-
and background workers — any process that computes `TaxCalculator(country: "UA", amount: 1000)`
|
|
144
|
-
once populates the cache for all others.
|
|
145
|
-
|
|
146
|
-
## Runtime Events
|
|
147
|
-
|
|
148
|
-
The resolver emits `:node_content_cache_hit` when a cached result is used:
|
|
149
|
-
|
|
150
|
-
```ruby
|
|
151
|
-
contract.execution.events.select { |e| e.type == :node_content_cache_hit }
|
|
152
|
-
# => [#<Event type=:node_content_cache_hit node=:tax ...>]
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Loading
|
|
156
|
-
|
|
157
|
-
```ruby
|
|
158
|
-
require "igniter/extensions/content_addressing"
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
This single require:
|
|
162
|
-
1. Loads `lib/igniter/content_addressing.rb` (ContentKey, Cache, module-level cache accessor).
|
|
163
|
-
2. Activates the resolver hooks via `lib/igniter/runtime/resolver.rb`.
|
|
164
|
-
|
|
165
|
-
Non-pure executors are completely unaffected — no overhead, no behavior change.
|
|
166
|
-
|
|
167
|
-
## Combining with Temporal Contracts
|
|
168
|
-
|
|
169
|
-
When used with [temporal contracts](TEMPORAL_V1.md), the `as_of` value is part of the
|
|
170
|
-
dependency hash and therefore part of the content key. Historical and current timestamps
|
|
171
|
-
produce distinct cache entries, and identical timestamps produce cache hits:
|
|
172
|
-
|
|
173
|
-
```ruby
|
|
174
|
-
require "igniter/temporal"
|
|
175
|
-
require "igniter/extensions/content_addressing"
|
|
176
|
-
|
|
177
|
-
class TaxRateExecutor < Igniter::Temporal::Executor
|
|
178
|
-
pure
|
|
179
|
-
fingerprint "tax_rate_v1"
|
|
180
|
-
|
|
181
|
-
def call(country:, as_of:)
|
|
182
|
-
RATES.dig(country, as_of.year) || 0.0
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Replaying a historical execution with the same `as_of` will hit the cache — the executor
|
|
188
|
-
is never called twice for the same (country, year) pair.
|
|
189
|
-
|
|
190
|
-
## Fingerprint Invalidation Pattern
|
|
191
|
-
|
|
192
|
-
When you deploy a fix to a `pure` executor, bump the fingerprint so the old cached
|
|
193
|
-
results are ignored immediately:
|
|
194
|
-
|
|
195
|
-
```ruby
|
|
196
|
-
# Before fix
|
|
197
|
-
class DiscountCalculator < Igniter::Executor
|
|
198
|
-
pure
|
|
199
|
-
fingerprint "discount_v1"
|
|
200
|
-
def call(amount:, code:) = amount * 0.9 # bug: off-by-one in rate
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# After fix
|
|
204
|
-
class DiscountCalculator < Igniter::Executor
|
|
205
|
-
pure
|
|
206
|
-
fingerprint "discount_v2" # <-- bumped; v1 cache entries are silently ignored
|
|
207
|
-
def call(amount:, code:) = amount * 0.85
|
|
208
|
-
end
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Old cache entries with `"discount_v1"` prefix in the key are never read again.
|
|
212
|
-
|
|
213
|
-
## Files
|
|
214
|
-
|
|
215
|
-
| File | Purpose |
|
|
216
|
-
|------|---------|
|
|
217
|
-
| `lib/igniter/content_addressing.rb` | `ContentKey`, `Cache`, module-level `cache` accessor |
|
|
218
|
-
| `lib/igniter/extensions/content_addressing.rb` | Entry point (`require "igniter/content_addressing"`) |
|
|
219
|
-
| `lib/igniter/executor.rb` | `pure`, `fingerprint`, `content_fingerprint`, `pure?` class DSL |
|
|
220
|
-
| `lib/igniter/runtime/resolver.rb` | `build_content_key` + cache fetch/store hooks in `resolve_compute` |
|
|
221
|
-
| `spec/igniter/content_addressing_spec.rb` | 19 examples |
|