igniter 0.4.5 → 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.
Files changed (638) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/README.md +164 -409
  4. data/bin/igniter-stack +94 -0
  5. data/docs/README.md +62 -0
  6. data/examples/README.md +74 -312
  7. data/examples/agent_orchestration.rb +76 -0
  8. data/examples/agents.rb +2 -1
  9. data/examples/catalog.rb +412 -0
  10. data/examples/consensus.rb +241 -0
  11. data/examples/dataflow.rb +309 -0
  12. data/examples/distributed_workflow.rb +1 -1
  13. data/examples/effects.rb +10 -9
  14. data/examples/elocal_webhook.rb +1 -0
  15. data/examples/incremental.rb +4 -3
  16. data/examples/introspection.rb +49 -0
  17. data/examples/invariants.rb +4 -3
  18. data/examples/llm_tools.rb +237 -0
  19. data/examples/mesh.rb +241 -0
  20. data/examples/mesh_discovery.rb +287 -0
  21. data/examples/mesh_gossip.rb +164 -0
  22. data/examples/reactive_auditing.rb +50 -0
  23. data/examples/ringcentral_routing.rb +1 -1
  24. data/examples/run.rb +163 -0
  25. data/lib/igniter/monorepo_packages.rb +17 -0
  26. data/lib/igniter/stack.rb +5 -0
  27. data/lib/igniter.rb +48 -15
  28. data/packages/igniter-agents/README.md +22 -0
  29. data/{lib → packages/igniter-agents/lib}/igniter/agent/ref.rb +1 -0
  30. data/{lib → packages/igniter-agents/lib}/igniter/agent/runner.rb +12 -0
  31. data/{lib → packages/igniter-agents/lib}/igniter/agent.rb +6 -0
  32. data/packages/igniter-agents/lib/igniter/agents/observability/metrics_agent.rb +130 -0
  33. data/packages/igniter-agents/lib/igniter/agents/pipeline/batch_processor_agent.rb +131 -0
  34. data/packages/igniter-agents/lib/igniter/agents/proactive/alert_agent.rb +111 -0
  35. data/packages/igniter-agents/lib/igniter/agents/proactive/health_check_agent.rb +122 -0
  36. data/packages/igniter-agents/lib/igniter/agents/proactive_agent.rb +208 -0
  37. data/packages/igniter-agents/lib/igniter/agents/reliability/retry_agent.rb +99 -0
  38. data/packages/igniter-agents/lib/igniter/agents/scheduling/cron_agent.rb +110 -0
  39. data/packages/igniter-agents/lib/igniter/agents.rb +23 -0
  40. data/packages/igniter-agents/lib/igniter/ai/agents/chain_agent.rb +129 -0
  41. data/packages/igniter-agents/lib/igniter/ai/agents/critic_agent.rb +165 -0
  42. data/packages/igniter-agents/lib/igniter/ai/agents/evaluator_agent.rb +195 -0
  43. data/packages/igniter-agents/lib/igniter/ai/agents/evolution_agent.rb +288 -0
  44. data/packages/igniter-agents/lib/igniter/ai/agents/observer_agent.rb +186 -0
  45. data/packages/igniter-agents/lib/igniter/ai/agents/planner_agent.rb +212 -0
  46. data/packages/igniter-agents/lib/igniter/ai/agents/router_agent.rb +133 -0
  47. data/packages/igniter-agents/lib/igniter/ai/agents/self_reflection_agent.rb +177 -0
  48. data/packages/igniter-agents/lib/igniter/ai/agents.rb +25 -0
  49. data/{lib → packages/igniter-agents/lib}/igniter/registry.rb +2 -0
  50. data/packages/igniter-agents/lib/igniter/runtime/registry_agent_adapter.rb +102 -0
  51. data/{lib → packages/igniter-agents/lib}/igniter/supervisor.rb +3 -0
  52. data/packages/igniter-agents/lib/igniter-agents.rb +7 -0
  53. data/packages/igniter-ai/README.md +20 -0
  54. data/packages/igniter-ai/lib/igniter/ai/config.rb +113 -0
  55. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/context.rb +2 -2
  56. data/packages/igniter-ai/lib/igniter/ai/executor.rb +352 -0
  57. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/anthropic.rb +42 -9
  58. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/base.rb +1 -1
  59. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/ollama.rb +4 -4
  60. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/openai.rb +39 -10
  61. data/packages/igniter-ai/lib/igniter/ai/skill/feedback.rb +116 -0
  62. data/packages/igniter-ai/lib/igniter/ai/skill/output_schema.rb +110 -0
  63. data/packages/igniter-ai/lib/igniter/ai/skill/runtime_contract.rb +87 -0
  64. data/packages/igniter-ai/lib/igniter/ai/skill.rb +107 -0
  65. data/packages/igniter-ai/lib/igniter/ai/tool_registry.rb +79 -0
  66. data/packages/igniter-ai/lib/igniter/ai/transcription/providers/assemblyai.rb +200 -0
  67. data/packages/igniter-ai/lib/igniter/ai/transcription/providers/base.rb +122 -0
  68. data/packages/igniter-ai/lib/igniter/ai/transcription/providers/deepgram.rb +162 -0
  69. data/packages/igniter-ai/lib/igniter/ai/transcription/providers/openai.rb +102 -0
  70. data/packages/igniter-ai/lib/igniter/ai/transcription/transcriber.rb +145 -0
  71. data/packages/igniter-ai/lib/igniter/ai/transcription/transcript_result.rb +29 -0
  72. data/packages/igniter-ai/lib/igniter/ai.rb +98 -0
  73. data/packages/igniter-ai/lib/igniter-ai.rb +3 -0
  74. data/packages/igniter-app/README.md +19 -0
  75. data/packages/igniter-app/lib/igniter/app/app_config.rb +43 -0
  76. data/packages/igniter-app/lib/igniter/app/app_host.rb +56 -0
  77. data/packages/igniter-app/lib/igniter/app/app_host_config.rb +27 -0
  78. data/packages/igniter-app/lib/igniter/app/app_host_pack.rb +13 -0
  79. data/packages/igniter-app/lib/igniter/app/autoloader.rb +18 -0
  80. data/packages/igniter-app/lib/igniter/app/cluster_app_host.rb +95 -0
  81. data/packages/igniter-app/lib/igniter/app/cluster_app_host_config.rb +78 -0
  82. data/packages/igniter-app/lib/igniter/app/credentials/config_loader.rb +152 -0
  83. data/packages/igniter-app/lib/igniter/app/credentials/credential.rb +48 -0
  84. data/packages/igniter-app/lib/igniter/app/credentials/credential_policy.rb +38 -0
  85. data/packages/igniter-app/lib/igniter/app/credentials/events/credential_event.rb +179 -0
  86. data/packages/igniter-app/lib/igniter/app/credentials/events.rb +12 -0
  87. data/packages/igniter-app/lib/igniter/app/credentials/lease_request.rb +153 -0
  88. data/packages/igniter-app/lib/igniter/app/credentials/policies/ephemeral_lease_policy.rb +35 -0
  89. data/packages/igniter-app/lib/igniter/app/credentials/policies/local_only_policy.rb +33 -0
  90. data/packages/igniter-app/lib/igniter/app/credentials/policies.rb +13 -0
  91. data/packages/igniter-app/lib/igniter/app/credentials/store.rb +21 -0
  92. data/packages/igniter-app/lib/igniter/app/credentials/stores/file_store.rb +114 -0
  93. data/packages/igniter-app/lib/igniter/app/credentials/trail.rb +254 -0
  94. data/packages/igniter-app/lib/igniter/app/credentials.rb +20 -0
  95. data/packages/igniter-app/lib/igniter/app/dev_output_sync.rb +4 -0
  96. data/packages/igniter-app/lib/igniter/app/diagnostics/app_host_contributor.rb +71 -0
  97. data/packages/igniter-app/lib/igniter/app/diagnostics/cluster_app_host_contributor.rb +97 -0
  98. data/packages/igniter-app/lib/igniter/app/diagnostics/credential_contributor.rb +66 -0
  99. data/packages/igniter-app/lib/igniter/app/diagnostics/evolution_contributor.rb +74 -0
  100. data/packages/igniter-app/lib/igniter/app/diagnostics/ignite_contributor.rb +121 -0
  101. data/packages/igniter-app/lib/igniter/app/diagnostics/loader_contributor.rb +68 -0
  102. data/packages/igniter-app/lib/igniter/app/diagnostics/orchestration_contributor.rb +200 -0
  103. data/packages/igniter-app/lib/igniter/app/diagnostics/runtime_contributor.rb +68 -0
  104. data/packages/igniter-app/lib/igniter/app/diagnostics/scheduler_contributor.rb +72 -0
  105. data/packages/igniter-app/lib/igniter/app/diagnostics/sdk_contributor.rb +284 -0
  106. data/packages/igniter-app/lib/igniter/app/diagnostics.rb +62 -0
  107. data/packages/igniter-app/lib/igniter/app/evolution/approval_decision.rb +115 -0
  108. data/packages/igniter-app/lib/igniter/app/evolution/approval_request.rb +36 -0
  109. data/packages/igniter-app/lib/igniter/app/evolution/plan.rb +72 -0
  110. data/packages/igniter-app/lib/igniter/app/evolution/planner.rb +85 -0
  111. data/packages/igniter-app/lib/igniter/app/evolution/result.rb +45 -0
  112. data/packages/igniter-app/lib/igniter/app/evolution/runner.rb +102 -0
  113. data/packages/igniter-app/lib/igniter/app/evolution/store.rb +21 -0
  114. data/packages/igniter-app/lib/igniter/app/evolution/stores/file_store.rb +241 -0
  115. data/packages/igniter-app/lib/igniter/app/evolution/trail.rb +108 -0
  116. data/packages/igniter-app/lib/igniter/app/evolution.rb +11 -0
  117. data/packages/igniter-app/lib/igniter/app/filesystem_loader_adapter.rb +21 -0
  118. data/packages/igniter-app/lib/igniter/app/generator.rb +636 -0
  119. data/packages/igniter-app/lib/igniter/app/generators/cluster.rb +1367 -0
  120. data/packages/igniter-app/lib/igniter/app/generators/dashboard.rb +152 -0
  121. data/packages/igniter-app/lib/igniter/app/generators/playground.rb +1227 -0
  122. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/README.md.erb +37 -0
  123. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/app.rb.erb +19 -0
  124. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/contexts/home_context.rb.erb +54 -0
  125. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/frontend/application.js.erb +3 -0
  126. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/spec/dashboard_app_spec.rb.erb +79 -0
  127. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/support/stack_overview.rb.erb +23 -0
  128. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/handlers/home_handler.rb.erb +27 -0
  129. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/home_page.arb.erb +44 -0
  130. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/home_page.rb.erb +56 -0
  131. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/layout.arb.erb +17 -0
  132. data/packages/igniter-app/lib/igniter/app/host_adapter.rb +26 -0
  133. data/packages/igniter-app/lib/igniter/app/host_config.rb +40 -0
  134. data/packages/igniter-app/lib/igniter/app/host_registry.rb +43 -0
  135. data/packages/igniter-app/lib/igniter/app/loader_adapter.rb +15 -0
  136. data/packages/igniter-app/lib/igniter/app/loader_pack.rb +8 -0
  137. data/packages/igniter-app/lib/igniter/app/loader_registry.rb +39 -0
  138. data/packages/igniter-app/lib/igniter/app/observability/operator_action_handler.rb +147 -0
  139. data/packages/igniter-app/lib/igniter/app/observability/operator_console_handler.rb +747 -0
  140. data/packages/igniter-app/lib/igniter/app/observability/operator_overview_handler.rb +350 -0
  141. data/packages/igniter-app/lib/igniter/app/observability.rb +5 -0
  142. data/packages/igniter-app/lib/igniter/app/observability_pack.rb +71 -0
  143. data/packages/igniter-app/lib/igniter/app/operator/dispatcher.rb +40 -0
  144. data/packages/igniter-app/lib/igniter/app/operator/handler_registry.rb +40 -0
  145. data/packages/igniter-app/lib/igniter/app/operator/handler_result.rb +67 -0
  146. data/packages/igniter-app/lib/igniter/app/operator/handlers/base.rb +79 -0
  147. data/packages/igniter-app/lib/igniter/app/operator/handlers/ignite_handler.rb +108 -0
  148. data/packages/igniter-app/lib/igniter/app/operator/handlers/orchestration_handler.rb +33 -0
  149. data/packages/igniter-app/lib/igniter/app/operator/handlers.rb +5 -0
  150. data/packages/igniter-app/lib/igniter/app/operator/lifecycle_contract.rb +55 -0
  151. data/packages/igniter-app/lib/igniter/app/operator/policy.rb +157 -0
  152. data/packages/igniter-app/lib/igniter/app/operator.rb +17 -0
  153. data/packages/igniter-app/lib/igniter/app/orchestration/action_result_builder.rb +65 -0
  154. data/packages/igniter-app/lib/igniter/app/orchestration/followup_request.rb +36 -0
  155. data/packages/igniter-app/lib/igniter/app/orchestration/handler_registry.rb +58 -0
  156. data/packages/igniter-app/lib/igniter/app/orchestration/handlers.rb +106 -0
  157. data/packages/igniter-app/lib/igniter/app/orchestration/inbox.rb +283 -0
  158. data/packages/igniter-app/lib/igniter/app/orchestration/inbox_query.rb +293 -0
  159. data/packages/igniter-app/lib/igniter/app/orchestration/lane_registry.rb +100 -0
  160. data/packages/igniter-app/lib/igniter/app/orchestration/operator_query.rb +449 -0
  161. data/packages/igniter-app/lib/igniter/app/orchestration/plan.rb +68 -0
  162. data/packages/igniter-app/lib/igniter/app/orchestration/planner.rb +89 -0
  163. data/packages/igniter-app/lib/igniter/app/orchestration/policies.rb +125 -0
  164. data/packages/igniter-app/lib/igniter/app/orchestration/policy_registry.rb +63 -0
  165. data/packages/igniter-app/lib/igniter/app/orchestration/result.rb +43 -0
  166. data/packages/igniter-app/lib/igniter/app/orchestration/routing_registry.rb +43 -0
  167. data/packages/igniter-app/lib/igniter/app/orchestration/runner.rb +50 -0
  168. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_event_query.rb +205 -0
  169. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_overview_builder.rb +286 -0
  170. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_query_overview_builder.rb +20 -0
  171. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_result_builder.rb +23 -0
  172. data/packages/igniter-app/lib/igniter/app/orchestration.rb +113 -0
  173. data/packages/igniter-app/lib/igniter/app/runtime.rb +4 -0
  174. data/packages/igniter-app/lib/igniter/app/runtime_context.rb +101 -0
  175. data/packages/igniter-app/lib/igniter/app/runtime_pack.rb +16 -0
  176. data/packages/igniter-app/lib/igniter/app/scaffold_pack.rb +6 -0
  177. data/packages/igniter-app/lib/igniter/app/scheduler.rb +109 -0
  178. data/packages/igniter-app/lib/igniter/app/scheduler_adapter.rb +17 -0
  179. data/packages/igniter-app/lib/igniter/app/scheduler_pack.rb +8 -0
  180. data/packages/igniter-app/lib/igniter/app/scheduler_registry.rb +39 -0
  181. data/packages/igniter-app/lib/igniter/app/stack.rb +1726 -0
  182. data/packages/igniter-app/lib/igniter/app/stack_pack.rb +3 -0
  183. data/packages/igniter-app/lib/igniter/app/threaded_scheduler_adapter.rb +35 -0
  184. data/packages/igniter-app/lib/igniter/app/yml_loader.rb +43 -0
  185. data/packages/igniter-app/lib/igniter/app.rb +2367 -0
  186. data/packages/igniter-app/lib/igniter/ignite/bootstrap_agent.rb +334 -0
  187. data/packages/igniter-app/lib/igniter/ignite/bootstrap_target.rb +79 -0
  188. data/packages/igniter-app/lib/igniter/ignite/deployment_intent.rb +82 -0
  189. data/packages/igniter-app/lib/igniter/ignite/ignition_agent.rb +1011 -0
  190. data/packages/igniter-app/lib/igniter/ignite/ignition_plan.rb +83 -0
  191. data/packages/igniter-app/lib/igniter/ignite/ignition_report.rb +144 -0
  192. data/packages/igniter-app/lib/igniter/ignite/store.rb +19 -0
  193. data/packages/igniter-app/lib/igniter/ignite/stores/file_store.rb +112 -0
  194. data/packages/igniter-app/lib/igniter/ignite/trail.rb +215 -0
  195. data/packages/igniter-app/lib/igniter/ignite.rb +11 -0
  196. data/packages/igniter-app/lib/igniter-app.rb +5 -0
  197. data/packages/igniter-cluster/README.md +9 -0
  198. data/packages/igniter-cluster/lib/igniter/cluster/agent_route_resolver.rb +58 -0
  199. data/packages/igniter-cluster/lib/igniter/cluster/consensus/cluster.rb +187 -0
  200. data/packages/igniter-cluster/lib/igniter/cluster/consensus/errors.rb +16 -0
  201. data/packages/igniter-cluster/lib/igniter/cluster/consensus/executors.rb +45 -0
  202. data/packages/igniter-cluster/lib/igniter/cluster/consensus/node.rb +322 -0
  203. data/packages/igniter-cluster/lib/igniter/cluster/consensus/read_query.rb +32 -0
  204. data/packages/igniter-cluster/lib/igniter/cluster/consensus/state_machine.rb +60 -0
  205. data/packages/igniter-cluster/lib/igniter/cluster/consensus.rb +18 -0
  206. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/governance_contributor.rb +90 -0
  207. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/identity_contributor.rb +98 -0
  208. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/routing_contributor.rb +674 -0
  209. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics.rb +24 -0
  210. data/packages/igniter-cluster/lib/igniter/cluster/events/envelope.rb +136 -0
  211. data/packages/igniter-cluster/lib/igniter/cluster/events/hook_support.rb +33 -0
  212. data/packages/igniter-cluster/lib/igniter/cluster/events/log.rb +102 -0
  213. data/packages/igniter-cluster/lib/igniter/cluster/events/projection_feed.rb +98 -0
  214. data/packages/igniter-cluster/lib/igniter/cluster/events/read_model_projector.rb +32 -0
  215. data/packages/igniter-cluster/lib/igniter/cluster/events.rb +131 -0
  216. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_decision.rb +41 -0
  217. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_policy.rb +66 -0
  218. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_queue.rb +88 -0
  219. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_request.rb +62 -0
  220. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_workflow.rb +214 -0
  221. data/packages/igniter-cluster/lib/igniter/cluster/governance/checkpoint.rb +141 -0
  222. data/packages/igniter-cluster/lib/igniter/cluster/governance/compaction_record.rb +33 -0
  223. data/packages/igniter-cluster/lib/igniter/cluster/governance/stores/checkpoint_store.rb +89 -0
  224. data/packages/igniter-cluster/lib/igniter/cluster/governance/stores/file_store.rb +249 -0
  225. data/packages/igniter-cluster/lib/igniter/cluster/governance/trail.rb +164 -0
  226. data/packages/igniter-cluster/lib/igniter/cluster/governance.rb +12 -0
  227. data/packages/igniter-cluster/lib/igniter/cluster/identity/capability_attestation.rb +114 -0
  228. data/packages/igniter-cluster/lib/igniter/cluster/identity/manifest.rb +139 -0
  229. data/packages/igniter-cluster/lib/igniter/cluster/identity/node_identity.rb +106 -0
  230. data/packages/igniter-cluster/lib/igniter/cluster/identity.rb +5 -0
  231. data/packages/igniter-cluster/lib/igniter/cluster/mesh/announcer.rb +88 -0
  232. data/packages/igniter-cluster/lib/igniter/cluster/mesh/checkpoint_gossip.rb +60 -0
  233. data/packages/igniter-cluster/lib/igniter/cluster/mesh/config.rb +146 -0
  234. data/packages/igniter-cluster/lib/igniter/cluster/mesh/discovery.rb +44 -0
  235. data/packages/igniter-cluster/lib/igniter/cluster/mesh/errors.rb +36 -0
  236. data/packages/igniter-cluster/lib/igniter/cluster/mesh/gossip.rb +62 -0
  237. data/packages/igniter-cluster/lib/igniter/cluster/mesh/mesh_ql.rb +470 -0
  238. data/packages/igniter-cluster/lib/igniter/cluster/mesh/node_observation.rb +281 -0
  239. data/packages/igniter-cluster/lib/igniter/cluster/mesh/observation_query.rb +284 -0
  240. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer.rb +51 -0
  241. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_capacity_report.rb +42 -0
  242. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_identity_envelope.rb +158 -0
  243. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_metadata.rb +122 -0
  244. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_registry.rb +81 -0
  245. data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_decision.rb +64 -0
  246. data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_planner.rb +154 -0
  247. data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_policy.rb +103 -0
  248. data/packages/igniter-cluster/lib/igniter/cluster/mesh/poller.rb +92 -0
  249. data/packages/igniter-cluster/lib/igniter/cluster/mesh/rebalance_plan.rb +66 -0
  250. data/packages/igniter-cluster/lib/igniter/cluster/mesh/rebalance_planner.rb +153 -0
  251. data/packages/igniter-cluster/lib/igniter/cluster/mesh/repair_loop.rb +169 -0
  252. data/packages/igniter-cluster/lib/igniter/cluster/mesh/router.rb +306 -0
  253. data/packages/igniter-cluster/lib/igniter/cluster/mesh/workload_signal.rb +46 -0
  254. data/packages/igniter-cluster/lib/igniter/cluster/mesh/workload_tracker.rb +215 -0
  255. data/packages/igniter-cluster/lib/igniter/cluster/mesh.rb +452 -0
  256. data/packages/igniter-cluster/lib/igniter/cluster/ownership/claim.rb +69 -0
  257. data/packages/igniter-cluster/lib/igniter/cluster/ownership/errors.rb +19 -0
  258. data/packages/igniter-cluster/lib/igniter/cluster/ownership/owner_client.rb +76 -0
  259. data/packages/igniter-cluster/lib/igniter/cluster/ownership/registry.rb +98 -0
  260. data/packages/igniter-cluster/lib/igniter/cluster/ownership/resolver.rb +62 -0
  261. data/packages/igniter-cluster/lib/igniter/cluster/ownership.rb +81 -0
  262. data/packages/igniter-cluster/lib/igniter/cluster/projection_store.rb +62 -0
  263. data/packages/igniter-cluster/lib/igniter/cluster/rag/chunk.rb +49 -0
  264. data/packages/igniter-cluster/lib/igniter/cluster/rag/fanout_retriever.rb +93 -0
  265. data/packages/igniter-cluster/lib/igniter/cluster/rag/knowledge_shard.rb +140 -0
  266. data/packages/igniter-cluster/lib/igniter/cluster/rag/net_http_adapter.rb +85 -0
  267. data/packages/igniter-cluster/lib/igniter/cluster/rag/ranker.rb +46 -0
  268. data/packages/igniter-cluster/lib/igniter/cluster/rag/retrieval_query.rb +30 -0
  269. data/packages/igniter-cluster/lib/igniter/cluster/rag/retrieval_result.rb +77 -0
  270. data/packages/igniter-cluster/lib/igniter/cluster/rag.rb +38 -0
  271. data/packages/igniter-cluster/lib/igniter/cluster/remote_adapter.rb +101 -0
  272. data/packages/igniter-cluster/lib/igniter/cluster/replication/bootstrapper.rb +63 -0
  273. data/packages/igniter-cluster/lib/igniter/cluster/replication/bootstrappers/gem.rb +39 -0
  274. data/packages/igniter-cluster/lib/igniter/cluster/replication/bootstrappers/git.rb +46 -0
  275. data/packages/igniter-cluster/lib/igniter/cluster/replication/bootstrappers/tarball.rb +62 -0
  276. data/packages/igniter-cluster/lib/igniter/cluster/replication/capability_query.rb +675 -0
  277. data/packages/igniter-cluster/lib/igniter/cluster/replication/expansion_plan.rb +40 -0
  278. data/packages/igniter-cluster/lib/igniter/cluster/replication/expansion_planner.rb +154 -0
  279. data/packages/igniter-cluster/lib/igniter/cluster/replication/manifest.rb +47 -0
  280. data/packages/igniter-cluster/lib/igniter/cluster/replication/network_topology.rb +150 -0
  281. data/packages/igniter-cluster/lib/igniter/cluster/replication/node_profile.rb +134 -0
  282. data/packages/igniter-cluster/lib/igniter/cluster/replication/reflective_replication_agent.rb +259 -0
  283. data/packages/igniter-cluster/lib/igniter/cluster/replication/replication_agent.rb +89 -0
  284. data/packages/igniter-cluster/lib/igniter/cluster/replication/ssh_session.rb +79 -0
  285. data/packages/igniter-cluster/lib/igniter/cluster/replication.rb +38 -0
  286. data/packages/igniter-cluster/lib/igniter/cluster/routed_agent_adapter.rb +79 -0
  287. data/packages/igniter-cluster/lib/igniter/cluster/routing_plan_executor.rb +427 -0
  288. data/packages/igniter-cluster/lib/igniter/cluster/routing_plan_result.rb +38 -0
  289. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_plan.rb +34 -0
  290. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_planner.rb +76 -0
  291. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_result.rb +34 -0
  292. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_runner.rb +125 -0
  293. data/packages/igniter-cluster/lib/igniter/cluster/trust/trust_assessment.rb +37 -0
  294. data/packages/igniter-cluster/lib/igniter/cluster/trust/trust_store.rb +58 -0
  295. data/packages/igniter-cluster/lib/igniter/cluster/trust/verifier.rb +80 -0
  296. data/packages/igniter-cluster/lib/igniter/cluster/trust.rb +9 -0
  297. data/packages/igniter-cluster/lib/igniter/cluster.rb +71 -0
  298. data/packages/igniter-cluster/lib/igniter-cluster.rb +3 -0
  299. data/packages/igniter-core/README.md +21 -0
  300. data/packages/igniter-core/lib/igniter/core/capabilities.rb +70 -0
  301. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/compiled_graph.rb +40 -2
  302. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validation_pipeline.rb +1 -0
  303. data/packages/igniter-core/lib/igniter/core/compiler/validators/agent_validator.rb +142 -0
  304. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/dependencies_validator.rb +90 -3
  305. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/remote_validator.rb +2 -0
  306. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler.rb +1 -0
  307. data/packages/igniter-core/lib/igniter/core/content_addressing.rb +133 -0
  308. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/contract.rb +88 -6
  309. data/packages/igniter-core/lib/igniter/core/dataflow/aggregate_operators.rb +147 -0
  310. data/packages/igniter-core/lib/igniter/core/dataflow/aggregate_state.rb +77 -0
  311. data/packages/igniter-core/lib/igniter/core/dataflow/diff.rb +37 -0
  312. data/packages/igniter-core/lib/igniter/core/dataflow/diff_state.rb +81 -0
  313. data/packages/igniter-core/lib/igniter/core/dataflow/incremental_collection_result.rb +39 -0
  314. data/packages/igniter-core/lib/igniter/core/dataflow/window_filter.rb +48 -0
  315. data/packages/igniter-core/lib/igniter/core/dataflow.rb +65 -0
  316. data/packages/igniter-core/lib/igniter/core/diagnostics/agent_contributor.rb +241 -0
  317. data/packages/igniter-core/lib/igniter/core/diagnostics/capability_contributor.rb +162 -0
  318. data/packages/igniter-core/lib/igniter/core/diagnostics/orchestration_contributor.rb +75 -0
  319. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/report.rb +81 -6
  320. data/packages/igniter-core/lib/igniter/core/diagnostics.rb +58 -0
  321. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl/contract_builder.rb +230 -9
  322. data/packages/igniter-core/lib/igniter/core/dto/record.rb +189 -0
  323. data/packages/igniter-core/lib/igniter/core/dto.rb +8 -0
  324. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/effect.rb +4 -0
  325. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/errors.rb +26 -3
  326. data/packages/igniter-core/lib/igniter/core/executor.rb +134 -0
  327. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection/graph_formatter.rb +32 -1
  328. data/packages/igniter-core/lib/igniter/core/extensions/introspection/plan_formatter.rb +85 -0
  329. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection/runtime_formatter.rb +26 -0
  330. data/packages/igniter-core/lib/igniter/core/extensions/invariants.rb +70 -0
  331. data/packages/igniter-core/lib/igniter/core/fingerprint.rb +43 -0
  332. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental.rb +4 -4
  333. data/packages/igniter-core/lib/igniter/core/memory/agent_memory.rb +104 -0
  334. data/packages/igniter-core/lib/igniter/core/memory/episode.rb +29 -0
  335. data/packages/igniter-core/lib/igniter/core/memory/fact.rb +27 -0
  336. data/packages/igniter-core/lib/igniter/core/memory/memorable.rb +90 -0
  337. data/packages/igniter-core/lib/igniter/core/memory/reflection_cycle.rb +96 -0
  338. data/packages/igniter-core/lib/igniter/core/memory/reflection_record.rb +28 -0
  339. data/packages/igniter-core/lib/igniter/core/memory/store.rb +115 -0
  340. data/packages/igniter-core/lib/igniter/core/memory/stores/in_memory.rb +136 -0
  341. data/packages/igniter-core/lib/igniter/core/memory/stores/sqlite.rb +286 -0
  342. data/packages/igniter-core/lib/igniter/core/memory.rb +80 -0
  343. data/packages/igniter-core/lib/igniter/core/metrics/collector.rb +131 -0
  344. data/packages/igniter-core/lib/igniter/core/metrics/prometheus_exporter.rb +104 -0
  345. data/packages/igniter-core/lib/igniter/core/metrics/snapshot.rb +8 -0
  346. data/packages/igniter-core/lib/igniter/core/metrics.rb +37 -0
  347. data/packages/igniter-core/lib/igniter/core/model/agent_interaction_contract.rb +172 -0
  348. data/packages/igniter-core/lib/igniter/core/model/agent_node.rb +86 -0
  349. data/packages/igniter-core/lib/igniter/core/model/aggregate_node.rb +34 -0
  350. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/branch_node.rb +37 -1
  351. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/collection_node.rb +3 -2
  352. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/compute_node.rb +13 -0
  353. data/packages/igniter-core/lib/igniter/core/model/remote_node.rb +91 -0
  354. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model.rb +2 -0
  355. data/packages/igniter-core/lib/igniter/core/node_cache.rb +231 -0
  356. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing.rb +8 -8
  357. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/builder.rb +30 -1
  358. data/packages/igniter-core/lib/igniter/core/runtime/agent_adapter.rb +41 -0
  359. data/packages/igniter-core/lib/igniter/core/runtime/agent_result_contract.rb +91 -0
  360. data/packages/igniter-core/lib/igniter/core/runtime/agent_route.rb +60 -0
  361. data/packages/igniter-core/lib/igniter/core/runtime/agent_route_resolver.rb +26 -0
  362. data/packages/igniter-core/lib/igniter/core/runtime/agent_session.rb +922 -0
  363. data/packages/igniter-core/lib/igniter/core/runtime/agent_session_query.rb +379 -0
  364. data/packages/igniter-core/lib/igniter/core/runtime/agent_transport.rb +30 -0
  365. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/cache.rb +6 -3
  366. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/deferred_result.rb +27 -1
  367. data/packages/igniter-core/lib/igniter/core/runtime/execution.rb +913 -0
  368. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/input_validator.rb +6 -2
  369. data/packages/igniter-core/lib/igniter/core/runtime/job_worker.rb +39 -0
  370. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/node_state.rb +4 -2
  371. data/packages/igniter-core/lib/igniter/core/runtime/orchestration_overview.rb +213 -0
  372. data/packages/igniter-core/lib/igniter/core/runtime/orchestration_runtime_state.rb +176 -0
  373. data/packages/igniter-core/lib/igniter/core/runtime/orchestration_transition_query.rb +208 -0
  374. data/packages/igniter-core/lib/igniter/core/runtime/planner.rb +301 -0
  375. data/packages/igniter-core/lib/igniter/core/runtime/proxy_agent_adapter.rb +124 -0
  376. data/packages/igniter-core/lib/igniter/core/runtime/remote_adapter.rb +26 -0
  377. data/packages/igniter-core/lib/igniter/core/runtime/resolver.rb +951 -0
  378. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/result.rb +2 -0
  379. data/packages/igniter-core/lib/igniter/core/runtime/stores/redis_store.rb +93 -0
  380. data/packages/igniter-core/lib/igniter/core/runtime/stores/sqlite_store.rb +155 -0
  381. data/packages/igniter-core/lib/igniter/core/runtime/stream_result.rb +171 -0
  382. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime.rb +15 -0
  383. data/packages/igniter-core/lib/igniter/core/temporal.rb +84 -0
  384. data/packages/igniter-core/lib/igniter/core/tool/discoverable.rb +151 -0
  385. data/packages/igniter-core/lib/igniter/core/tool.rb +56 -0
  386. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/version.rb +1 -1
  387. data/packages/igniter-core/lib/igniter/core.rb +23 -0
  388. data/packages/igniter-core/lib/igniter-core.rb +3 -0
  389. data/packages/igniter-extensions/README.md +21 -0
  390. data/packages/igniter-extensions/lib/igniter/extensions/auditing.rb +3 -0
  391. data/packages/igniter-extensions/lib/igniter/extensions/capabilities.rb +39 -0
  392. data/packages/igniter-extensions/lib/igniter/extensions/content_addressing.rb +5 -0
  393. data/packages/igniter-extensions/lib/igniter/extensions/dataflow.rb +117 -0
  394. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/differential.rb +1 -1
  395. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/execution_report.rb +1 -1
  396. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/incremental.rb +1 -1
  397. data/packages/igniter-extensions/lib/igniter/extensions/introspection.rb +3 -0
  398. data/packages/igniter-extensions/lib/igniter/extensions/invariants.rb +3 -0
  399. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/provenance.rb +1 -1
  400. data/packages/igniter-extensions/lib/igniter/extensions/reactive.rb +3 -0
  401. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/saga.rb +1 -1
  402. data/packages/igniter-extensions/lib/igniter/extensions.rb +8 -0
  403. data/packages/igniter-extensions/lib/igniter-extensions.rb +3 -0
  404. data/packages/igniter-frontend/README.md +224 -0
  405. data/packages/igniter-frontend/lib/igniter/frontend/app.rb +90 -0
  406. data/packages/igniter-frontend/lib/igniter/frontend/app_access.rb +36 -0
  407. data/packages/igniter-frontend/lib/igniter/frontend/arbre/component.rb +120 -0
  408. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/action_group.rb +53 -0
  409. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/badge.rb +91 -0
  410. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/boolean.rb +53 -0
  411. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/breadcrumbs.rb +71 -0
  412. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/card.rb +114 -0
  413. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/conversation_panel.rb +61 -0
  414. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/datetime.rb +42 -0
  415. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/display_value_support.rb +38 -0
  416. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/empty_state.rb +39 -0
  417. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/event_list.rb +44 -0
  418. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/filters.rb +183 -0
  419. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/indicator.rb +59 -0
  420. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/json_panel.rb +36 -0
  421. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/key_value_list.rb +40 -0
  422. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/loading_state.rb +43 -0
  423. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/metric_grid.rb +37 -0
  424. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/number.rb +53 -0
  425. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/page_header.rb +53 -0
  426. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/pagination.rb +143 -0
  427. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/panel.rb +67 -0
  428. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/percentage.rb +79 -0
  429. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/resource_list.rb +38 -0
  430. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/scenario_card.rb +48 -0
  431. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/shell_columns.rb +67 -0
  432. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/sidebar_shell.rb +106 -0
  433. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/table_with.rb +203 -0
  434. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/tabs.rb +147 -0
  435. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/viz.rb +185 -0
  436. data/packages/igniter-frontend/lib/igniter/frontend/arbre/page.rb +74 -0
  437. data/packages/igniter-frontend/lib/igniter/frontend/arbre/raw_text_node.rb +40 -0
  438. data/packages/igniter-frontend/lib/igniter/frontend/arbre/template_page.rb +243 -0
  439. data/packages/igniter-frontend/lib/igniter/frontend/arbre.rb +48 -0
  440. data/packages/igniter-frontend/lib/igniter/frontend/arbre_page.rb +7 -0
  441. data/packages/igniter-frontend/lib/igniter/frontend/assets.rb +101 -0
  442. data/packages/igniter-frontend/lib/igniter/frontend/builder.rb +124 -0
  443. data/packages/igniter-frontend/lib/igniter/frontend/component.rb +24 -0
  444. data/packages/igniter-frontend/lib/igniter/frontend/components.rb +7 -0
  445. data/packages/igniter-frontend/lib/igniter/frontend/context.rb +53 -0
  446. data/packages/igniter-frontend/lib/igniter/frontend/form_builder.rb +63 -0
  447. data/packages/igniter-frontend/lib/igniter/frontend/handler.rb +92 -0
  448. data/packages/igniter-frontend/lib/igniter/frontend/javascript.rb +353 -0
  449. data/packages/igniter-frontend/lib/igniter/frontend/page.rb +24 -0
  450. data/packages/igniter-frontend/lib/igniter/frontend/request.rb +61 -0
  451. data/packages/igniter-frontend/lib/igniter/frontend/response.rb +67 -0
  452. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime/adapters.rb +226 -0
  453. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime/presets.rb +147 -0
  454. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime.rb +259 -0
  455. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/surfaces.rb +1074 -0
  456. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/ui.rb +1438 -0
  457. data/packages/igniter-frontend/lib/igniter/frontend/tailwind.rb +180 -0
  458. data/packages/igniter-frontend/lib/igniter/frontend/version.rb +9 -0
  459. data/packages/igniter-frontend/lib/igniter/frontend.rb +35 -0
  460. data/packages/igniter-frontend/lib/igniter-frontend.rb +3 -0
  461. data/packages/igniter-rails/README.md +96 -0
  462. data/packages/igniter-rails/lib/igniter/plugins/rails/generators/contract/templates/contract.rb.tt +22 -0
  463. data/packages/igniter-rails/lib/igniter/plugins/rails/generators/install/templates/igniter.rb.tt +16 -0
  464. data/packages/igniter-rails/lib/igniter-rails.rb +3 -0
  465. data/packages/igniter-schema-rendering/README.md +27 -0
  466. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/page.rb +35 -0
  467. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/patcher.rb +47 -0
  468. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/renderer.rb +268 -0
  469. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/schema.rb +172 -0
  470. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/store.rb +53 -0
  471. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_normalizer.rb +117 -0
  472. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_processor.rb +91 -0
  473. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_validator.rb +62 -0
  474. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/version.rb +9 -0
  475. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering.rb +20 -0
  476. data/packages/igniter-schema-rendering/lib/igniter-schema-rendering.rb +3 -0
  477. data/packages/igniter-sdk/README.md +25 -0
  478. data/packages/igniter-sdk/lib/igniter/sdk/channels/base.rb +84 -0
  479. data/packages/igniter-sdk/lib/igniter/sdk/channels/delivery_result.rb +61 -0
  480. data/packages/igniter-sdk/lib/igniter/sdk/channels/message.rb +101 -0
  481. data/packages/igniter-sdk/lib/igniter/sdk/channels/telegram.rb +161 -0
  482. data/packages/igniter-sdk/lib/igniter/sdk/channels/webhook.rb +213 -0
  483. data/packages/igniter-sdk/lib/igniter/sdk/channels.rb +17 -0
  484. data/packages/igniter-sdk/lib/igniter/sdk/data/store.rb +31 -0
  485. data/packages/igniter-sdk/lib/igniter/sdk/data/stores/file.rb +113 -0
  486. data/packages/igniter-sdk/lib/igniter/sdk/data/stores/in_memory.rb +63 -0
  487. data/packages/igniter-sdk/lib/igniter/sdk/data/stores/sqlite.rb +144 -0
  488. data/packages/igniter-sdk/lib/igniter/sdk/data.rb +34 -0
  489. data/packages/igniter-sdk/lib/igniter/sdk/tools/agent_bootstrap_tool.rb +151 -0
  490. data/packages/igniter-sdk/lib/igniter/sdk/tools/local_workflow_selector_tool.rb +269 -0
  491. data/packages/igniter-sdk/lib/igniter/sdk/tools/system_discovery_tool.rb +198 -0
  492. data/packages/igniter-sdk/lib/igniter/sdk/tools.rb +9 -0
  493. data/packages/igniter-sdk/lib/igniter/sdk.rb +86 -0
  494. data/packages/igniter-sdk/lib/igniter-sdk.rb +3 -0
  495. data/packages/igniter-server/README.md +9 -0
  496. data/packages/igniter-server/lib/igniter/server/agent_session_store.rb +98 -0
  497. data/packages/igniter-server/lib/igniter/server/agent_transport.rb +95 -0
  498. data/packages/igniter-server/lib/igniter/server/app_host.rb +3 -0
  499. data/packages/igniter-server/lib/igniter/server/client.rb +256 -0
  500. data/packages/igniter-server/lib/igniter/server/config.rb +70 -0
  501. data/packages/igniter-server/lib/igniter/server/handlers/agent_message_handler.rb +107 -0
  502. data/packages/igniter-server/lib/igniter/server/handlers/agent_session_handler.rb +125 -0
  503. data/{lib → packages/igniter-server/lib}/igniter/server/handlers/event_handler.rb +4 -0
  504. data/{lib → packages/igniter-server/lib}/igniter/server/handlers/execute_handler.rb +6 -0
  505. data/packages/igniter-server/lib/igniter/server/handlers/liveness_handler.rb +20 -0
  506. data/packages/igniter-server/lib/igniter/server/handlers/manifest_handler.rb +77 -0
  507. data/packages/igniter-server/lib/igniter/server/handlers/metrics_handler.rb +51 -0
  508. data/packages/igniter-server/lib/igniter/server/handlers/peers_handler.rb +136 -0
  509. data/packages/igniter-server/lib/igniter/server/handlers/readiness_handler.rb +47 -0
  510. data/packages/igniter-server/lib/igniter/server/http_server.rb +222 -0
  511. data/{lib → packages/igniter-server/lib}/igniter/server/rack_app.rb +27 -2
  512. data/packages/igniter-server/lib/igniter/server/remote_adapter.rb +27 -0
  513. data/packages/igniter-server/lib/igniter/server/router.rb +291 -0
  514. data/packages/igniter-server/lib/igniter/server/server_logger.rb +54 -0
  515. data/packages/igniter-server/lib/igniter/server.rb +130 -0
  516. data/packages/igniter-server/lib/igniter-server.rb +3 -0
  517. metadata +638 -168
  518. data/docs/API_V2.md +0 -537
  519. data/docs/ARCHITECTURE_V2.md +0 -317
  520. data/docs/BACKLOG.md +0 -166
  521. data/docs/BRANCHES_V1.md +0 -213
  522. data/docs/COLLECTIONS_V1.md +0 -303
  523. data/docs/DISTRIBUTED_CONTRACTS_V1.md +0 -493
  524. data/docs/EXECUTION_MODEL_V2.md +0 -324
  525. data/docs/IGNITER_CONCEPTS.md +0 -81
  526. data/docs/LLM_V1.md +0 -335
  527. data/docs/PATTERNS.md +0 -411
  528. data/docs/SERVER_V1.md +0 -313
  529. data/docs/STORE_ADAPTERS.md +0 -126
  530. data/lib/igniter/diagnostics.rb +0 -8
  531. data/lib/igniter/executor.rb +0 -74
  532. data/lib/igniter/extensions/introspection/plan_formatter.rb +0 -55
  533. data/lib/igniter/extensions/invariants.rb +0 -116
  534. data/lib/igniter/integrations/agents.rb +0 -18
  535. data/lib/igniter/integrations/llm/config.rb +0 -69
  536. data/lib/igniter/integrations/llm/executor.rb +0 -159
  537. data/lib/igniter/integrations/llm.rb +0 -59
  538. data/lib/igniter/model/remote_node.rb +0 -26
  539. data/lib/igniter/runtime/execution.rb +0 -398
  540. data/lib/igniter/runtime/job_worker.rb +0 -18
  541. data/lib/igniter/runtime/planner.rb +0 -126
  542. data/lib/igniter/runtime/resolver.rb +0 -520
  543. data/lib/igniter/runtime/stores/redis_store.rb +0 -56
  544. data/lib/igniter/server/client.rb +0 -123
  545. data/lib/igniter/server/config.rb +0 -27
  546. data/lib/igniter/server/http_server.rb +0 -109
  547. data/lib/igniter/server/router.rb +0 -75
  548. data/lib/igniter/server.rb +0 -67
  549. /data/{lib → packages/igniter-agents/lib}/igniter/agent/mailbox.rb +0 -0
  550. /data/{lib → packages/igniter-agents/lib}/igniter/agent/message.rb +0 -0
  551. /data/{lib → packages/igniter-agents/lib}/igniter/agent/state_holder.rb +0 -0
  552. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/graph_compiler.rb +0 -0
  553. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/type_resolver.rb +0 -0
  554. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validation_context.rb +0 -0
  555. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validator.rb +0 -0
  556. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/await_validator.rb +0 -0
  557. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/callable_validator.rb +0 -0
  558. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/outputs_validator.rb +0 -0
  559. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/type_compatibility_validator.rb +0 -0
  560. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/uniqueness_validator.rb +0 -0
  561. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/auditing/report/console_formatter.rb +0 -0
  562. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/auditing/report/markdown_formatter.rb +0 -0
  563. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/introspection/formatters/mermaid_formatter.rb +0 -0
  564. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/introspection/formatters/text_tree_formatter.rb +0 -0
  565. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/divergence.rb +0 -0
  566. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/formatter.rb +0 -0
  567. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/report.rb +0 -0
  568. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/runner.rb +0 -0
  569. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential.rb +0 -0
  570. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl/schema_builder.rb +0 -0
  571. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl.rb +0 -0
  572. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/effect_registry.rb +0 -0
  573. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events/bus.rb +0 -0
  574. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events/event.rb +0 -0
  575. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events.rb +0 -0
  576. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/builder.rb +0 -0
  577. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/formatter.rb +0 -0
  578. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/node_entry.rb +0 -0
  579. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/report.rb +0 -0
  580. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report.rb +0 -0
  581. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/executor_registry.rb +0 -0
  582. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/auditing/timeline.rb +0 -0
  583. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/auditing.rb +0 -0
  584. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection.rb +0 -0
  585. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/engine.rb +0 -0
  586. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/matcher.rb +0 -0
  587. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/reaction.rb +0 -0
  588. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive.rb +0 -0
  589. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions.rb +0 -0
  590. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/formatter.rb +0 -0
  591. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/result.rb +0 -0
  592. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/tracker.rb +0 -0
  593. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/invariant.rb +0 -0
  594. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/await_node.rb +0 -0
  595. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/composition_node.rb +0 -0
  596. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/effect_node.rb +0 -0
  597. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/graph.rb +0 -0
  598. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/input_node.rb +0 -0
  599. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/node.rb +0 -0
  600. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/output_node.rb +0 -0
  601. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/formatter.rb +0 -0
  602. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/generators.rb +0 -0
  603. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/result.rb +0 -0
  604. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/run.rb +0 -0
  605. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/runner.rb +0 -0
  606. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/lineage.rb +0 -0
  607. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/node_trace.rb +0 -0
  608. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/text_formatter.rb +0 -0
  609. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance.rb +0 -0
  610. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/collection_result.rb +0 -0
  611. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/invalidator.rb +0 -0
  612. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runner_factory.rb +0 -0
  613. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/inline_runner.rb +0 -0
  614. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/store_runner.rb +0 -0
  615. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/thread_pool_runner.rb +0 -0
  616. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/active_record_store.rb +0 -0
  617. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/file_store.rb +0 -0
  618. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/memory_store.rb +0 -0
  619. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/compensation.rb +0 -0
  620. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/compensation_record.rb +0 -0
  621. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/executor.rb +0 -0
  622. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/formatter.rb +0 -0
  623. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/result.rb +0 -0
  624. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga.rb +0 -0
  625. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/stream_loop.rb +0 -0
  626. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/type_system.rb +0 -0
  627. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/cable_adapter.rb +0 -0
  628. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/contract_job.rb +0 -0
  629. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/generators/contract/contract_generator.rb +0 -0
  630. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/generators/install/install_generator.rb +0 -0
  631. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/railtie.rb +0 -0
  632. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/webhook_concern.rb +0 -0
  633. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails.rb +0 -0
  634. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/base.rb +0 -0
  635. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/contracts_handler.rb +0 -0
  636. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/health_handler.rb +0 -0
  637. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/status_handler.rb +0 -0
  638. /data/{lib → packages/igniter-server/lib}/igniter/server/registry.rb +0 -0
@@ -0,0 +1,1074 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Igniter
6
+ module Frontend
7
+ module Tailwind
8
+ module Surfaces
9
+ class Preset
10
+ attr_reader :name
11
+ attr_reader :theme_name
12
+ attr_reader :realtime_preset
13
+ attr_reader :components
14
+ attr_reader :hooks
15
+
16
+ def initialize(name:, theme_name:, realtime_preset: nil, components: [], hooks: {})
17
+ @name = name.to_sym
18
+ @theme_name = theme_name.to_sym
19
+ @realtime_preset = realtime_preset
20
+ @components = components.map(&:to_sym).freeze
21
+ @hooks = hooks.transform_keys(&:to_sym).freeze
22
+ end
23
+
24
+ def theme
25
+ Tailwind::UI::Theme.fetch(theme_name)
26
+ end
27
+
28
+ def render_head(head, config:)
29
+ realtime_preset&.render_head(head, config: config)
30
+ end
31
+
32
+ def metric_card(id:, label:, value:, hint: nil)
33
+ Tailwind::UI::MetricCard.new(
34
+ label: label,
35
+ value: value,
36
+ hint: hint,
37
+ wrapper_attributes: { data: { metric_id: id } },
38
+ value_attributes: { data: { metric_value: id } }
39
+ )
40
+ end
41
+
42
+ def panel(title:, subtitle: nil, &block)
43
+ theme.panel(title: title, subtitle: subtitle, &block)
44
+ end
45
+
46
+ def ops_hero_actions(view, endpoints:)
47
+ view.component(
48
+ Tailwind::UI::ActionBar.new(
49
+ class_name: theme.hero(:dashboard).fetch(:action_bar_class)
50
+ ) do |links|
51
+ endpoints.each do |endpoint|
52
+ label = endpoint[:label] || endpoint["label"]
53
+ path = endpoint[:path] || endpoint["path"]
54
+ links.tag(:a,
55
+ label,
56
+ href: path,
57
+ class: Tailwind::UI::Tokens.action(
58
+ variant: :soft,
59
+ theme: :amber,
60
+ size: :sm,
61
+ extra: "pill-link font-medium"
62
+ ))
63
+ end
64
+ end
65
+ )
66
+ end
67
+
68
+ def operations_pulse(view, generated_at:, poll_interval_seconds:, charts:)
69
+ view.component(
70
+ theme.live_badge(
71
+ label: "Realtime overview",
72
+ value: generated_at,
73
+ interval_seconds: poll_interval_seconds
74
+ )
75
+ )
76
+ view.tag(:h3, "Device Status", class: theme.section_heading_class)
77
+ view.component(theme.bar_chart(items: normalize_chart_items(charts.fetch(:device_status)), chart_id: "device-status"))
78
+ view.tag(:h3, "Activity Mix", class: theme.section_heading_class)
79
+ view.component(theme.bar_chart(items: normalize_chart_items(charts.fetch(:activity_mix)), chart_id: "activity-mix"))
80
+ view.tag(:h3, "App Role Spread", class: theme.section_heading_class)
81
+ view.component(theme.bar_chart(items: normalize_chart_items(charts.fetch(:app_roles)), chart_id: "app-roles"))
82
+ end
83
+
84
+ def realtime_feed(view, stream_path:, events:, waiting_message: "Waiting for live events...")
85
+ view.tag(:p,
86
+ "Stream source #{stream_path}",
87
+ class: "#{theme.muted_text_class(extra: "muted")} mb-3",
88
+ data: { realtime_stream_path: stream_path })
89
+ view.tag(:ul, **realtime_feed_attributes(item_class: theme.compact_card_class)) do |list|
90
+ if events.empty?
91
+ list.tag(:li, waiting_message, class: theme.compact_card_class)
92
+ else
93
+ events.each do |entry|
94
+ list.tag(:li,
95
+ "#{entry.fetch("title")} · #{entry.fetch("detail")}",
96
+ class: theme.compact_card_class)
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def chat_prompt_bar(view, prompts:)
103
+ view.tag(:div, class: "mb-5 flex flex-wrap gap-2") do |actions|
104
+ prompts.each do |prompt|
105
+ actions.tag(:button,
106
+ prompt,
107
+ type: "button",
108
+ class: Tailwind::UI::Tokens.action(
109
+ variant: :ghost,
110
+ theme: :amber,
111
+ size: :sm,
112
+ extra: "justify-start text-left"
113
+ ),
114
+ data: { chat_prompt: prompt })
115
+ end
116
+ end
117
+ end
118
+
119
+ def activity_filter_bar(view, items:)
120
+ view.component(
121
+ Tailwind::UI::ActionBar.new(class_name: "hero-links mb-4 flex flex-wrap gap-3") do |links|
122
+ items.each do |item|
123
+ links.tag(:a,
124
+ item.fetch(:label),
125
+ href: item.fetch(:href),
126
+ class: item.fetch(:active) ? active_filter_class : filter_link_class)
127
+ end
128
+ end
129
+ )
130
+ end
131
+
132
+ def timeline_focus_actions(view, source_url:, clear_path:)
133
+ view.component(
134
+ Tailwind::UI::ActionBar.new(
135
+ tag: :p,
136
+ class_name: "mt-4 flex flex-wrap gap-3"
137
+ ) do |paragraph|
138
+ paragraph.tag(:a,
139
+ "Open source API",
140
+ href: source_url,
141
+ class: Tailwind::UI::Tokens.action(variant: :soft, theme: :cyan, size: :sm))
142
+ paragraph.tag(:a,
143
+ "Clear focus",
144
+ href: clear_path,
145
+ class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :amber, size: :sm))
146
+ end
147
+ )
148
+ end
149
+
150
+ def device_inventory(view, devices:, empty_message:)
151
+ if devices.empty?
152
+ view.tag(:p, empty_message, class: theme.empty_state_class(extra: "empty-state"))
153
+ return
154
+ end
155
+
156
+ view.tag(:ul, class: theme.list_class, data: { device_list: "true" }) do |list|
157
+ devices.each do |device|
158
+ list.tag(:li,
159
+ class: theme.list_item_class,
160
+ **device_item_attributes(id: device.fetch(:id), status: device.fetch(:status))) do |item|
161
+ item.tag(:strong, class: theme.item_title_class) do |heading|
162
+ heading.tag(:a, device.fetch(:title), href: device.fetch(:href), class: ops_title_link_class)
163
+ end
164
+ item.tag(:div, device.fetch(:subtitle), class: theme.muted_text_class(extra: "muted"))
165
+ item.tag(:code, device.fetch(:code), class: theme.code_class)
166
+ item.tag(:div, class: "mt-3 flex flex-wrap items-center gap-2") do |meta|
167
+ meta.component(
168
+ Tailwind::UI::StatusBadge.new(
169
+ label: device.fetch(:status),
170
+ html_attributes: device_status_badge_attributes(device.fetch(:id))
171
+ )
172
+ )
173
+ meta.tag(:span,
174
+ device.fetch(:last_seen),
175
+ class: theme.muted_text_class,
176
+ **device_last_seen_attributes(device.fetch(:id)))
177
+ end
178
+ next unless device[:telemetry]
179
+
180
+ item.tag(:div,
181
+ device.fetch(:telemetry),
182
+ class: "#{theme.muted_text_class(extra: "muted")} mt-2",
183
+ **device_telemetry_attributes(device.fetch(:id)))
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def devices_panel(devices:, empty_message:)
190
+ panel(
191
+ title: "Devices",
192
+ subtitle: "Declared device inventory and current route targets."
193
+ ) do |view|
194
+ device_inventory(view, devices: devices, empty_message: empty_message)
195
+ end
196
+ end
197
+
198
+ def notes_list(view, notes:, empty_message:)
199
+ view.tag(:ul, class: theme.list_class, **notes_list_attributes) do |list|
200
+ if notes.empty?
201
+ list.tag(:li, empty_message, class: theme.empty_state_class(extra: "empty-state"), data: { empty_state: "true" })
202
+ else
203
+ notes.each do |note|
204
+ list.tag(:li, class: theme.list_item_class, data: { note_id: note.fetch(:id) }) do |item|
205
+ item.tag(:strong, note.fetch(:title), class: theme.item_title_class)
206
+ item.tag(:div, note.fetch(:meta), class: "#{theme.muted_text_class(extra: "muted")} mt-2")
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ def notes_panel(notes:, empty_message:, error_message: nil, &block)
214
+ panel(
215
+ title: "Shared Notes",
216
+ subtitle: "Fast operator scratchpad shared between main and dashboard."
217
+ ) do |view|
218
+ if error_message
219
+ view.tag(:p,
220
+ error_message,
221
+ class: "error-banner mb-4 rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100")
222
+ end
223
+ block&.call(view)
224
+ notes_list(view, notes: notes, empty_message: empty_message)
225
+ end
226
+ end
227
+
228
+ def chat_transcript(view, messages:, empty_message:)
229
+ view.tag(:ul, class: theme.list_class, **chat_list_attributes) do |list|
230
+ if messages.empty?
231
+ list.tag(:li, empty_message, class: theme.empty_state_class(extra: "empty-state"), data: { empty_state: "true" })
232
+ else
233
+ messages.each do |message|
234
+ list.tag(:li, class: theme.list_item_class, data: { chat_turn_id: message.fetch(:id, "") }) do |item|
235
+ item.tag(:div, class: "flex flex-wrap items-center gap-2") do |meta|
236
+ meta.component(Tailwind::UI::StatusBadge.new(label: message.fetch(:role)))
237
+ meta.tag(:span, message.fetch(:meta), class: theme.muted_text_class)
238
+ end
239
+ item.tag(:div, message.fetch(:body), class: theme.body_text_class(extra: "mt-3 whitespace-pre-wrap"))
240
+ render_chat_action_card(item, message[:action]) if message[:action]
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ def chat_action_memory(view, queue:, history:)
248
+ return if queue.empty? && history.empty?
249
+
250
+ unless queue.empty?
251
+ view.tag(:h3, "Action Queue", class: theme.section_heading_class)
252
+ view.tag(:ul, class: theme.compact_list_class, data: { action_queue: "true" }) do |list|
253
+ queue.each do |entry|
254
+ list.tag(:li, class: "list-none", data: { action_key: entry.fetch(:action_key) }) do |item|
255
+ render_chat_action_card(item, entry, compact: true)
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ return if history.empty?
262
+
263
+ view.tag(:h3, "Recent Action Outcomes", class: theme.section_heading_class(extra: "mt-4"))
264
+ view.tag(:ul, class: theme.compact_list_class, data: { action_history: "true" }) do |list|
265
+ history.each do |entry|
266
+ list.tag(:li, class: "list-none", data: { action_key: entry.fetch(:action_key) }) do |item|
267
+ render_chat_action_card(item, entry, compact: true)
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ def chat_panel(messages:, prompts:, empty_message:, error_message: nil, action_queue: [], action_history: [], &block)
274
+ panel(
275
+ title: "Igniter Chat",
276
+ subtitle: "Thin operator chat loop over the current home-lab snapshot."
277
+ ) do |view|
278
+ if error_message
279
+ view.tag(:p,
280
+ error_message,
281
+ class: "mb-4 rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100")
282
+ end
283
+ block&.call(view)
284
+ chat_prompt_bar(view, prompts: prompts)
285
+ chat_action_memory(view, queue: action_queue, history: action_history)
286
+ chat_transcript(view, messages: messages, empty_message: empty_message)
287
+ end
288
+ end
289
+
290
+ def camera_events_list(view, events:, empty_message:)
291
+ view.tag(:ul, class: theme.list_class, **camera_events_list_attributes) do |list|
292
+ if events.empty?
293
+ list.tag(:li, empty_message, class: theme.empty_state_class(extra: "empty-state"), data: { empty_state: "true" })
294
+ else
295
+ events.each do |event|
296
+ list.tag(:li, class: theme.list_item_class, data: { camera_event_id: event.fetch(:id) }) do |item|
297
+ item.tag(:strong, event.fetch(:title), class: theme.item_title_class)
298
+ item.tag(:div, event.fetch(:meta), class: "#{theme.muted_text_class(extra: "muted")} mt-2")
299
+ item.tag(:div, event.fetch(:body), class: theme.body_text_class(extra: "mt-2 text-stone-200"))
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ def camera_events_panel(events:, empty_message:)
307
+ panel(
308
+ title: "Recent Camera Events",
309
+ subtitle: "Thin edge-ingest slice for ESP32-CAM style traffic."
310
+ ) do |view|
311
+ camera_events_list(view, events: events, empty_message: empty_message)
312
+ end
313
+ end
314
+
315
+ def activity_timeline(view, items:, empty_message:)
316
+ view.tag(:ul, class: theme.list_class, **activity_timeline_attributes) do |list|
317
+ if items.empty?
318
+ list.tag(:li, empty_message, class: theme.empty_state_class(extra: "empty-state"), data: { empty_state: "true" })
319
+ else
320
+ items.each do |entry|
321
+ list.tag(:li,
322
+ class: theme.list_item_class,
323
+ data: { timeline_entry_id: entry.fetch(:id), timeline_entry_type: entry.fetch(:type) }) do |item|
324
+ item.tag(:strong, class: theme.item_title_class) do |heading|
325
+ heading.tag(:a, entry.fetch(:title), href: entry.fetch(:href), class: ops_title_link_class)
326
+ end
327
+ item.tag(:div, entry.fetch(:detail), class: "#{theme.muted_text_class(extra: "muted")} mt-2")
328
+ item.tag(:div, entry.fetch(:age), class: "#{theme.muted_text_class(extra: "muted")} mt-2")
329
+ next unless entry[:source_url]
330
+
331
+ item.tag(:a,
332
+ "open source",
333
+ href: entry.fetch(:source_url),
334
+ class: Tailwind::UI::Tokens.underline_link(theme: :amber, extra: "mt-3 inline-flex text-sm"))
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
340
+
341
+ def timeline_panel(filter_items:, items:, empty_message:)
342
+ panel(
343
+ title: "Activity Timeline",
344
+ subtitle: "Merged recent notes, camera events, and device heartbeats."
345
+ ) do |view|
346
+ activity_filter_bar(view, items: filter_items)
347
+ activity_timeline(view, items: items, empty_message: empty_message)
348
+ end
349
+ end
350
+
351
+ def timeline_focus_panel(entry:, clear_path:, empty_message: "No focused item.")
352
+ panel(
353
+ title: "Timeline Focus",
354
+ subtitle: "Drilldown into the currently selected timeline item."
355
+ ) do |view|
356
+ unless entry
357
+ view.tag(:p, empty_message, class: theme.empty_state_class(extra: "empty-state"))
358
+ next
359
+ end
360
+
361
+ view.tag(:p, class: "flex flex-wrap items-center gap-2") do |paragraph|
362
+ paragraph.component(Tailwind::UI::StatusBadge.new(label: entry.fetch(:type)))
363
+ paragraph.tag(:span, entry.fetch(:title), class: theme.section_heading_class(extra: "mt-0 text-stone-200"))
364
+ end
365
+ view.tag(:p, entry.fetch(:detail), class: "#{theme.muted_text_class(extra: "muted")} mt-3")
366
+ view.tag(:p, "seen=#{entry.fetch(:seen)}", class: "#{theme.muted_text_class(extra: "muted")} mt-2")
367
+ timeline_focus_actions(view, source_url: entry.fetch(:source_url), clear_path: clear_path)
368
+ end
369
+ end
370
+
371
+ def health_readiness_panel(surfaces:, empty_message:)
372
+ panel(
373
+ title: "Health & Readiness",
374
+ subtitle: "Declared health surfaces for the currently modelled app stack."
375
+ ) do |view|
376
+ if surfaces.empty?
377
+ view.tag(:p, empty_message, class: theme.empty_state_class(extra: "empty-state"))
378
+ next
379
+ end
380
+
381
+ view.tag(:ul, class: theme.list_class) do |list|
382
+ surfaces.each do |surface|
383
+ list.tag(:li, class: theme.list_item_class) do |item|
384
+ item.tag(:strong, surface.fetch(:title), class: theme.item_title_class)
385
+ item.tag(:div, class: "mt-3 flex flex-wrap items-center gap-2") do |meta|
386
+ meta.component(Tailwind::UI::StatusBadge.new(label: surface.fetch(:status)))
387
+ meta.tag(:span, surface.fetch(:meta), class: theme.muted_text_class)
388
+ end
389
+ next unless surface[:url]
390
+
391
+ item.tag(:a,
392
+ surface.fetch(:url),
393
+ href: surface.fetch(:url),
394
+ class: Tailwind::UI::Tokens.underline_link(theme: :amber, extra: "mt-3 inline-flex text-sm"))
395
+ end
396
+ end
397
+ end
398
+ end
399
+ end
400
+
401
+ def topology_health_panel(health:)
402
+ panel(
403
+ title: "Topology Health",
404
+ subtitle: "High-level health verdict across devices and app surfaces."
405
+ ) do |view|
406
+ view.tag(:p, class: "flex flex-wrap items-center gap-2") do |paragraph|
407
+ paragraph.tag(:span, "overall=", class: theme.muted_text_class)
408
+ paragraph.component(
409
+ Tailwind::UI::StatusBadge.new(
410
+ label: health.fetch(:overall_status),
411
+ html_attributes: topology_overall_status_attributes
412
+ )
413
+ )
414
+ end
415
+ view.tag(:p, health.fetch(:readiness_summary), class: "#{theme.muted_text_class(extra: "muted")} mt-3")
416
+ if health[:acknowledgement_summary]
417
+ view.tag(:p,
418
+ health.fetch(:acknowledgement_summary),
419
+ class: "#{theme.muted_text_class(extra: "muted")} mt-2",
420
+ data: { topology_health_acknowledged: "true" })
421
+ end
422
+
423
+ view.tag(:ul, class: theme.compact_list_class) do |list|
424
+ health.fetch(:device_status_counts).each do |entry|
425
+ list.tag(:li,
426
+ entry.fetch(:label),
427
+ class: theme.compact_item_class,
428
+ **topology_device_count_attributes(entry.fetch(:status)))
429
+ end
430
+ end
431
+
432
+ alerts = health.fetch(:alerts)
433
+ next if alerts.empty?
434
+
435
+ view.tag(:h3, "Alerts", class: theme.section_heading_class)
436
+ view.tag(:ul, class: theme.compact_list_class) do |list|
437
+ alerts.each { |alert| list.tag(:li, alert, class: theme.compact_item_class) }
438
+ end
439
+ end
440
+ end
441
+
442
+ def network_topology_panel(topology_notes:, public_endpoints:, dependency_edges:)
443
+ panel(
444
+ title: "Network & Topology",
445
+ subtitle: "Ports, public endpoints, app edges, and topology notes."
446
+ ) do |view|
447
+ unless topology_notes.empty?
448
+ view.tag(:h3, "Topology Notes", class: theme.section_heading_class)
449
+ view.tag(:ul, class: theme.compact_list_class) do |list|
450
+ topology_notes.each { |note| list.tag(:li, note, class: theme.compact_item_class) }
451
+ end
452
+ end
453
+
454
+ view.tag(:h3, "Public Endpoints", class: theme.section_heading_class)
455
+ view.component(
456
+ theme.endpoint_list(
457
+ items: public_endpoints,
458
+ compact: true,
459
+ empty_message: "No public endpoints configured.",
460
+ link_class: Tailwind::UI::Tokens.underline_link(theme: :amber, extra: "text-sm")
461
+ )
462
+ )
463
+
464
+ view.tag(:h3, "App Edges", class: theme.section_heading_class)
465
+ if dependency_edges.empty?
466
+ view.tag(:p, "No inter-app dependencies yet.", class: theme.empty_state_class(extra: "empty-state"))
467
+ else
468
+ view.tag(:ul, class: theme.compact_list_class) do |list|
469
+ dependency_edges.each { |edge| list.tag(:li, edge, class: theme.compact_item_class) }
470
+ end
471
+ end
472
+ end
473
+ end
474
+
475
+ def app_services_panel(services:, empty_message:)
476
+ panel(
477
+ title: "App Services",
478
+ subtitle: "Runtime-facing app roles, classes, commands, and dependencies."
479
+ ) do |view|
480
+ if services.empty?
481
+ view.tag(:p, empty_message, class: theme.empty_state_class(extra: "empty-state"))
482
+ next
483
+ end
484
+
485
+ view.tag(:ul, class: theme.list_class) do |list|
486
+ services.each do |service|
487
+ list.tag(:li, class: theme.list_item_class) do |item|
488
+ item.tag(:strong, class: theme.item_title_class) do |heading|
489
+ heading.tag(:a, service.fetch(:title), href: service.fetch(:href), class: ops_title_link_class)
490
+ end
491
+ item.tag(:div, service.fetch(:meta), class: "#{theme.muted_text_class(extra: "muted")} mt-2")
492
+ item.tag(:code, service.fetch(:class_name), class: "#{theme.code_class} mt-2 block")
493
+ item.tag(:code, service.fetch(:command), class: "#{theme.code_class} mt-2 block")
494
+ item.tag(:div, service.fetch(:path), class: "#{theme.muted_text_class(extra: "muted")} mt-2") if service[:path]
495
+ item.tag(:div, service.fetch(:depends_on), class: "#{theme.muted_text_class(extra: "muted")} mt-2") if service[:depends_on]
496
+ end
497
+ end
498
+ end
499
+ end
500
+ end
501
+
502
+ def resources_panel(stores:, var_files:, total_var_bytes:)
503
+ panel(
504
+ title: "Resources",
505
+ subtitle: "Current workspace stores and local var files."
506
+ ) do |view|
507
+ view.tag(:h3, "Stores", class: theme.section_heading_class)
508
+ view.component(theme.resource_list(items: stores, compact: true))
509
+
510
+ view.tag(:h3, "Var Files", class: theme.section_heading_class)
511
+ view.component(
512
+ theme.resource_list(
513
+ items: var_files,
514
+ compact: true,
515
+ empty_message: "No var files yet."
516
+ )
517
+ )
518
+
519
+ view.tag(:p, "total_var_bytes=#{total_var_bytes}", class: "#{theme.muted_text_class(extra: "muted")} mt-3")
520
+ end
521
+ end
522
+
523
+ def debug_surfaces_panel(api_items:, files:, commands:)
524
+ panel(
525
+ title: "Debug Surfaces",
526
+ subtitle: "Useful entrypoints, file anchors, and local commands."
527
+ ) do |view|
528
+ view.tag(:h3, "APIs", class: theme.section_heading_class)
529
+ view.component(
530
+ theme.endpoint_list(
531
+ items: api_items,
532
+ compact: true,
533
+ link_class: Tailwind::UI::Tokens.underline_link(theme: :amber, extra: "text-sm")
534
+ )
535
+ )
536
+
537
+ view.tag(:h3, "Files", class: theme.section_heading_class)
538
+ compact_code_list(view, files)
539
+
540
+ view.tag(:h3, "Commands", class: theme.section_heading_class)
541
+ compact_code_list(view, commands)
542
+ end
543
+ end
544
+
545
+ def next_ideas_panel(ideas:, empty_message:)
546
+ panel(
547
+ title: "Next Ideas",
548
+ subtitle: "Likely next slices once this proving surface feels stable."
549
+ ) do |view|
550
+ if ideas.empty?
551
+ view.tag(:p, empty_message, class: theme.empty_state_class(extra: "empty-state"))
552
+ else
553
+ view.tag(:ul, class: theme.compact_list_class) do |list|
554
+ ideas.each { |idea| list.tag(:li, idea, class: theme.compact_item_class) }
555
+ end
556
+ end
557
+ end
558
+ end
559
+
560
+ def topology_flow_panel(diagram:, title: "Topology Mermaid", description: "Devices flow into edge, and app dependencies are overlaid as dotted links.")
561
+ panel(
562
+ title: "Topology Flow",
563
+ subtitle: "Mermaid view of devices, routes, and inter-app dependencies."
564
+ ) do |view|
565
+ view.component(
566
+ theme.mermaid_diagram(
567
+ diagram: diagram,
568
+ title: title,
569
+ description: description
570
+ )
571
+ )
572
+ end
573
+ end
574
+
575
+ def execution_flow_panel(diagram:, title: "Execution Mermaid", description: "Ingest and operator actions converge into shared stores and the stack overview loop.")
576
+ panel(
577
+ title: "Execution Flow",
578
+ subtitle: "Mermaid view of ingest, shared stores, stack overview, and dashboard loop."
579
+ ) do |view|
580
+ view.component(
581
+ theme.mermaid_diagram(
582
+ diagram: diagram,
583
+ title: title,
584
+ description: description
585
+ )
586
+ )
587
+ end
588
+ end
589
+
590
+ def form_section(title:, subtitle:, action:, method: "post", **options, &block)
591
+ theme.form_section(title: title, subtitle: subtitle, action: action, method: method, **options, &block)
592
+ end
593
+
594
+ def authoring_catalog_panel(&block)
595
+ panel(
596
+ title: "View Schemas",
597
+ subtitle: "Catalog browser and lightweight authoring surface for persisted schemas.",
598
+ &block
599
+ )
600
+ end
601
+
602
+ def schema_create_form_section(**options, &block)
603
+ form_section(
604
+ title: "Create Schema",
605
+ subtitle: "Post a full schema payload directly to the catalog API.",
606
+ action: "#",
607
+ **options,
608
+ &block
609
+ )
610
+ end
611
+
612
+ def schema_patch_form_section(**options, &block)
613
+ form_section(
614
+ title: "Patch Schema",
615
+ subtitle: "Load an existing schema, edit a JSON patch, then apply it through the catalog API.",
616
+ action: "#",
617
+ **options,
618
+ &block
619
+ )
620
+ end
621
+
622
+ def recent_submissions_panel(&block)
623
+ panel(
624
+ title: "Recent Submissions",
625
+ subtitle: "Latest schema runtime outputs flowing back into the operator surface.",
626
+ &block
627
+ )
628
+ end
629
+
630
+ def submission_summary_panel(&block)
631
+ panel(
632
+ title: "Summary",
633
+ subtitle: "Runtime-level context for this schema submission.",
634
+ &block
635
+ )
636
+ end
637
+
638
+ def submission_replay_panel(&block)
639
+ panel(
640
+ title: "Replay",
641
+ subtitle: "Replay the stored raw payload back into the originating schema action.",
642
+ &block
643
+ )
644
+ end
645
+
646
+ def submission_payload_panel(kind, &block)
647
+ case kind.to_sym
648
+ when :raw
649
+ panel(
650
+ title: "Raw Payload",
651
+ subtitle: "Original form values as they were submitted.",
652
+ &block
653
+ )
654
+ when :normalized
655
+ panel(
656
+ title: "Normalized Payload",
657
+ subtitle: "Payload after schema normalization and type coercion.",
658
+ &block
659
+ )
660
+ when :processing_result
661
+ panel(
662
+ title: "Processing Result",
663
+ subtitle: "Runtime result captured after submission processing.",
664
+ &block
665
+ )
666
+ else
667
+ raise ArgumentError, "unsupported submission payload panel kind: #{kind}"
668
+ end
669
+ end
670
+
671
+ def submission_diff_panel(&block)
672
+ panel(
673
+ title: "Normalization Diff",
674
+ subtitle: "Field-level view of what changed between raw and normalized payloads.",
675
+ &block
676
+ )
677
+ end
678
+
679
+ def schema_catalog_intro(view, description:, catalog_path:, featured_view_path:, featured_view_label:)
680
+ view.tag(:div, class: "space-y-4") do |container|
681
+ container.tag(:p, description, class: theme.body_text_class)
682
+ container.component(
683
+ Tailwind::UI::ActionBar.new(class_name: "flex flex-wrap gap-2") do |actions|
684
+ actions.tag(:a,
685
+ "Catalog JSON",
686
+ href: catalog_path,
687
+ class: Tailwind::UI::Tokens.action(variant: :soft, theme: :orange, size: :sm))
688
+ actions.tag(:a,
689
+ featured_view_label,
690
+ href: featured_view_path,
691
+ class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :orange, size: :sm))
692
+ end
693
+ )
694
+ end
695
+ end
696
+
697
+ def schema_catalog_list(view, items:, empty_message:)
698
+ if items.empty?
699
+ view.tag(:p, empty_message, class: theme.empty_state_class)
700
+ return
701
+ end
702
+
703
+ view.tag(:ul, class: theme.list_class) do |list|
704
+ items.each do |item|
705
+ list.tag(:li, class: theme.list_item_class) do |card|
706
+ card.tag(:strong, item.fetch(:title), class: theme.item_title_class)
707
+ card.tag(:div, item.fetch(:id), class: theme.muted_text_class(extra: "mt-2"))
708
+ card.tag(:div, item.fetch(:meta), class: theme.muted_text_class(extra: "mt-2"))
709
+ card.component(
710
+ Tailwind::UI::ActionBar.new(class_name: "mt-3 flex flex-wrap gap-2") do |actions|
711
+ actions.tag(:a,
712
+ "Open view",
713
+ href: item.fetch(:view_path),
714
+ class: Tailwind::UI::Tokens.action(variant: :soft, theme: :orange, size: :sm))
715
+ actions.tag(:a,
716
+ "JSON",
717
+ href: item.fetch(:api_path),
718
+ class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :orange, size: :sm))
719
+ actions.tag(:button,
720
+ item.fetch(:load_label, "Load JSON"),
721
+ type: "button",
722
+ class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :orange, size: :sm),
723
+ onclick: item.fetch(:load_action))
724
+ actions.tag(:button,
725
+ item.fetch(:clone_label, "Clone"),
726
+ type: "button",
727
+ class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :orange, size: :sm),
728
+ onclick: item.fetch(:clone_action))
729
+ end
730
+ )
731
+ end
732
+ end
733
+ end
734
+ end
735
+
736
+ def schema_catalog_grid_class
737
+ "grid gap-5 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)]"
738
+ end
739
+
740
+ def submission_timeline(view, description:, items:, empty_message:)
741
+ view.tag(:div, class: "space-y-4") do |container|
742
+ container.tag(:p, description, class: theme.body_text_class)
743
+ container.component(
744
+ theme.timeline_list(
745
+ items: items,
746
+ empty_message: empty_message,
747
+ title_link_class: Tailwind::UI::Tokens.underline_link(theme: :orange),
748
+ action_link_class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :orange, size: :sm)
749
+ )
750
+ )
751
+ end
752
+ end
753
+
754
+ def submission_replay_actions(view, source_view_path:, schema_path:)
755
+ view.component(
756
+ Tailwind::UI::ActionBar.new(class_name: "flex flex-wrap gap-2") do |bar|
757
+ bar.tag(:a,
758
+ "Open submission source view",
759
+ href: source_view_path,
760
+ class: Tailwind::UI::Tokens.action(variant: :soft, theme: :orange, size: :sm))
761
+ bar.tag(:a,
762
+ "Open schema JSON",
763
+ href: schema_path,
764
+ class: Tailwind::UI::Tokens.action(variant: :ghost, theme: :orange, size: :sm))
765
+ end
766
+ )
767
+ end
768
+
769
+ def submission_json_payload(view, payload)
770
+ view.tag(:pre, class: "#{theme.code_class} overflow-x-auto whitespace-pre-wrap") do |pre|
771
+ pre.text(JSON.pretty_generate(payload))
772
+ end
773
+ end
774
+
775
+ def submission_detail_grid_class
776
+ "grid gap-5 xl:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]"
777
+ end
778
+
779
+ def realtime_feed_attributes(item_class: theme.compact_card_class)
780
+ {
781
+ class: theme.compact_list_class,
782
+ data: {
783
+ realtime_feed: "true",
784
+ item_class: item_class
785
+ }
786
+ }
787
+ end
788
+
789
+ def notes_list_attributes
790
+ { data: { notes_list: "true" } }
791
+ end
792
+
793
+ def chat_list_attributes
794
+ { data: { chat_list: "true" } }
795
+ end
796
+
797
+ def camera_events_list_attributes
798
+ { data: { camera_events_list: "true" } }
799
+ end
800
+
801
+ def activity_timeline_attributes
802
+ { data: { activity_timeline: "true" } }
803
+ end
804
+
805
+ def device_item_attributes(id:, status:)
806
+ { data: { device_id: id.to_s, device_status: status.to_s } }
807
+ end
808
+
809
+ def device_status_badge_attributes(id)
810
+ { "data-device-status-badge": id.to_s }
811
+ end
812
+
813
+ def device_last_seen_attributes(id)
814
+ { data: { device_last_seen: id.to_s } }
815
+ end
816
+
817
+ def device_telemetry_attributes(id)
818
+ { data: { device_telemetry: id.to_s } }
819
+ end
820
+
821
+ def topology_overall_status_attributes
822
+ { "data-topology-overall-status": "true" }
823
+ end
824
+
825
+ def topology_device_count_attributes(status)
826
+ { data: { topology_device_count: status.to_s } }
827
+ end
828
+
829
+ private
830
+
831
+ def filter_link_class
832
+ Tailwind::UI::Tokens.action(variant: :ghost, theme: :amber, size: :sm, extra: "pill-link")
833
+ end
834
+
835
+ def active_filter_class
836
+ Tailwind::UI::Tokens.action(
837
+ variant: :primary,
838
+ theme: :amber,
839
+ size: :sm,
840
+ extra: "pill-link active-pill font-medium"
841
+ )
842
+ end
843
+
844
+ def normalize_chart_items(items)
845
+ items.map do |item|
846
+ {
847
+ key: item[:key] || item["key"],
848
+ label: item[:label] || item["label"],
849
+ value: item[:value] || item["value"]
850
+ }.compact
851
+ end
852
+ end
853
+
854
+ def ops_title_link_class
855
+ "transition hover:text-amber-200"
856
+ end
857
+
858
+ def compact_code_list(view, values)
859
+ view.tag(:ul, class: theme.compact_list_class) do |list|
860
+ values.each do |value|
861
+ list.tag(:li, class: theme.compact_card_class) { |item| item.tag(:code, value, class: theme.code_class) }
862
+ end
863
+ end
864
+ end
865
+
866
+ def render_chat_action_card(view, action, compact: false)
867
+ view.tag(
868
+ :div,
869
+ class: chat_action_card_class(action.fetch(:status), compact: compact),
870
+ data: { action_status: normalized_action_status(action.fetch(:status)) }
871
+ ) do |card|
872
+ card.tag(:div, class: "flex flex-wrap items-center gap-2") do |meta|
873
+ meta.tag(:strong, action.fetch(:title), class: theme.item_title_class)
874
+ meta.component(
875
+ Tailwind::UI::StatusBadge.new(
876
+ label: action.fetch(:status),
877
+ tone: chat_action_status_tone(action.fetch(:status)),
878
+ html_attributes: { data: { action_status_badge: normalized_action_status(action.fetch(:status)) } }
879
+ )
880
+ )
881
+ end
882
+ card.tag(:p, action.fetch(:preview), class: theme.body_text_class(extra: compact ? "mt-2" : "mt-3"))
883
+ card.tag(:p, action.fetch(:meta), class: theme.muted_text_class(extra: "mt-2")) if action[:meta]
884
+ render_chat_action_details(card, action[:details])
885
+ render_chat_action_payload(card, action[:payload], status: action.fetch(:status))
886
+
887
+ render_chat_action_card_actions(card, action)
888
+ end
889
+ end
890
+
891
+ def render_chat_action_card_actions(view, action)
892
+ actions = []
893
+ actions << action[:confirm] if action[:confirm]
894
+ actions << action[:dismiss] if action[:dismiss]
895
+ return if actions.empty?
896
+
897
+ view.tag(:div, class: "mt-4 flex flex-wrap gap-3") do |row|
898
+ actions.each do |entry|
899
+ row.form(action: entry.fetch(:path), method: entry.fetch(:method, "post"), class: "inline-flex") do |form|
900
+ entry.fetch(:hidden, {}).each { |name, value| form.hidden(name, value) }
901
+ form.submit(entry.fetch(:label), class: entry.fetch(:class_name))
902
+ end
903
+ end
904
+ end
905
+ end
906
+
907
+ def render_chat_action_details(view, details)
908
+ items = Array(details).compact
909
+ return if items.empty?
910
+
911
+ view.tag(:dl, class: "mt-3 grid gap-2 text-sm", data: { action_details: "true" }) do |list|
912
+ items.each do |detail|
913
+ next if detail[:value].to_s.strip.empty?
914
+
915
+ list.tag(:div, class: "grid gap-1 rounded-2xl border border-white/10 bg-black/10 px-3 py-2") do |row|
916
+ row.tag(:dt, detail.fetch(:label), class: "text-[11px] font-semibold uppercase tracking-[0.18em] text-stone-400")
917
+ row.tag(:dd, detail.fetch(:value), class: theme.body_text_class)
918
+ end
919
+ end
920
+ end
921
+ end
922
+
923
+ def render_chat_action_payload(view, payload, status:)
924
+ data = payload.is_a?(Hash) ? payload : nil
925
+ return if data.nil? || data.empty?
926
+
927
+ label = normalized_action_status(status) == "confirmation_required" ? "Proposed Payload" : "Result Payload"
928
+ view.tag(:div, class: "mt-3", data: { action_payload: "true" }) do |section|
929
+ section.tag(:p, label, class: "text-[11px] font-semibold uppercase tracking-[0.18em] text-stone-400")
930
+ section.tag(:pre, class: "#{theme.code_class} mt-2 overflow-x-auto whitespace-pre-wrap") do |code|
931
+ code.text(JSON.pretty_generate(data))
932
+ end
933
+ end
934
+ end
935
+
936
+ def chat_action_card_class(status, compact:)
937
+ base = compact ? "rounded-2xl border p-4" : "mt-4 rounded-2xl border p-4"
938
+ [base, chat_action_card_tone_class(status)].join(" ")
939
+ end
940
+
941
+ def chat_action_card_tone_class(status)
942
+ case normalized_action_status(status)
943
+ when "confirmation_required"
944
+ "border-amber-300/25 bg-amber-300/10"
945
+ when "completed"
946
+ "border-emerald-300/25 bg-emerald-300/10"
947
+ when "dismissed"
948
+ "border-stone-300/15 bg-stone-400/5"
949
+ when "unavailable"
950
+ "border-rose-300/25 bg-rose-300/10"
951
+ else
952
+ "border-white/10 bg-white/5"
953
+ end
954
+ end
955
+
956
+ def chat_action_status_tone(status)
957
+ case normalized_action_status(status)
958
+ when "confirmation_required"
959
+ "border-amber-300/30 bg-amber-300/10 text-amber-100"
960
+ when "completed"
961
+ "border-emerald-300/30 bg-emerald-300/10 text-emerald-100"
962
+ when "dismissed"
963
+ "border-stone-300/20 bg-stone-400/10 text-stone-200"
964
+ when "unavailable"
965
+ "border-rose-300/30 bg-rose-300/10 text-rose-100"
966
+ else
967
+ "border-cyan-300/30 bg-cyan-300/10 text-cyan-100"
968
+ end
969
+ end
970
+
971
+ def normalized_action_status(status)
972
+ status.to_s.strip.downcase.tr(" ", "_")
973
+ end
974
+ end
975
+
976
+ module_function
977
+
978
+ def ops_dashboard
979
+ theme = Tailwind::UI::Theme.fetch(:ops)
980
+ Preset.new(
981
+ name: :ops_dashboard,
982
+ theme_name: :ops,
983
+ realtime_preset: Tailwind::Realtime::Presets.home_ops(theme: theme),
984
+ components: %i[
985
+ metric_card
986
+ panel
987
+ live_badge
988
+ bar_chart
989
+ mermaid_diagram
990
+ status_badge
991
+ endpoint_list
992
+ timeline_list
993
+ ops_hero_actions
994
+ operations_pulse
995
+ realtime_feed
996
+ chat_prompt_bar
997
+ activity_filter_bar
998
+ timeline_focus_actions
999
+ device_inventory
1000
+ notes_list
1001
+ chat_transcript
1002
+ camera_events_list
1003
+ activity_timeline
1004
+ devices_panel
1005
+ notes_panel
1006
+ chat_panel
1007
+ camera_events_panel
1008
+ timeline_panel
1009
+ timeline_focus_panel
1010
+ health_readiness_panel
1011
+ topology_health_panel
1012
+ network_topology_panel
1013
+ app_services_panel
1014
+ resources_panel
1015
+ debug_surfaces_panel
1016
+ next_ideas_panel
1017
+ topology_flow_panel
1018
+ execution_flow_panel
1019
+ ],
1020
+ hooks: {
1021
+ metrics: %w[apps public-apps devices devices-online heartbeats notes chat-turns camera-events motion-events],
1022
+ lists: %w[notes chat camera_events activity_timeline],
1023
+ device_presence: %w[device_id device_status device_last_seen device_telemetry],
1024
+ topology_health: %w[topology_overall_status topology_device_count],
1025
+ realtime_feed: "realtime_feed"
1026
+ }
1027
+ )
1028
+ end
1029
+
1030
+ def schema_authoring
1031
+ Preset.new(
1032
+ name: :schema_authoring,
1033
+ theme_name: :companion,
1034
+ components: %i[
1035
+ panel
1036
+ form_section
1037
+ banner
1038
+ key_value_list
1039
+ action_bar
1040
+ message_page
1041
+ schema_catalog
1042
+ submission_timeline
1043
+ ],
1044
+ hooks: {
1045
+ catalog: %w[view_schema_catalog schema_json_link schema_editor],
1046
+ submissions: %w[submission_browser submission_detail_link]
1047
+ }
1048
+ )
1049
+ end
1050
+
1051
+ def submission_inspection
1052
+ Preset.new(
1053
+ name: :submission_inspection,
1054
+ theme_name: :companion,
1055
+ components: %i[
1056
+ payload_diff
1057
+ key_value_list
1058
+ panel
1059
+ message_page
1060
+ action_bar
1061
+ code_block
1062
+ submission_replay_actions
1063
+ submission_json_payload
1064
+ ],
1065
+ hooks: {
1066
+ payloads: %w[raw_payload normalized_payload processing_result],
1067
+ diffs: %w[payload_diff normalization_diff]
1068
+ }
1069
+ )
1070
+ end
1071
+ end
1072
+ end
1073
+ end
1074
+ end