Class: Insika::Server::App

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/server/app.rb

Overview

Rack app. Transports ONLY translate requests into Commands — the server holds no business logic. It parses JSON, builds Command.build(...), dispatches on the CommandBus and projects the Event Stream to SSE. Reads are NOT Commands: they are direct reads from the stores.

AUDITABLE constitutional rule: server/ does not import the Executor, store WRITE methods, or RubyLLM. Requires: json, rack, async and the core types (Command/Event/errors) already loaded by the composition root.

Constant Summary collapse

SSE_HEADERS =
{
  "content-type" => "text/event-stream",
  "cache-control" => "no-cache",
  "connection" => "keep-alive"
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(command_bus:, event_stream:, session_store:, task_store:, config:, pending_action_store: nil, a2a: nil, provisioner: nil, workflow_registry: nil, onboarding: nil, profiles: nil, channels: nil) ⇒ App

The operator control UI now lives in the Studio (§12 G5); server/ is a pure transport surface (/v1, /a2a). The constitutional rule holds: server/ only READS stores and never imports the Executor, store writes, or RubyLLM.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/insika/server/app.rb', line 47

def initialize(command_bus:, event_stream:, session_store:, task_store:,
               config:, pending_action_store: nil, a2a: nil, provisioner: nil,
               workflow_registry: nil, onboarding: nil, profiles: nil,
               channels: nil)
  @command_bus = command_bus
  @event_stream = event_stream
  @session_store = session_store
  @task_store = task_store
  @config = config
  @pending_action_store = pending_action_store # read for GET /v1/tasks/:id
  @a2a = a2a # A2A edge. nil = server does not expose A2A (parity).
  @provisioner = provisioner # PackImporter. nil = provisioning not exposed.
  # Item 22 / §4.4: READ-ONLY registry, injected only where workflows are
  # exposed (the minimal wiring). nil = no /v1/workflows routes (parity — the
  # deployment does not expose workflows). Reading a catalog is a READ, like a
  # store read: the constitutional rule (no Executor/store-writes/RubyLLM) holds.
  @workflow_registry = workflow_registry
  # Item 20 / §5.6: LLM-first onboarding surface (start.md + models.json +
  # docs). PUBLIC (no auth — the whole point of the "read <base>/start.md" trick
  # is that the developer's coding agent can fetch it), and READ-ONLY, so the
  # constitutional rule holds. nil = routes not exposed (parity — the production
  # deployment opts in). Reading files/masked stores is a READ, like a store read.
  @onboarding = onboarding
  # RFC-0014 §3.2: READ-ONLY ProfileSource, so `GET /v1/agents/:id` can answer
  # what an agent has — the eval is a client and cannot read a store. Same
  # constitutional footing as the workflow registry: reading a catalog is a
  # READ. nil = the route 404s (parity).
  @profiles = profiles
  # RFC-0011 §4.4: ONE generic route family for every channel, opt-in by
  # injecting the registry (nil ⇒ the routes do not exist, parity with @a2a).
  # The channel does the translating; this class keeps doing only transport.
  @channels = channels
  @heartbeat = config.fetch(:heartbeat, 15)
  @sync_timeout = config.fetch(:sync_timeout, 10) # synchronous control
end

Instance Method Details

#call(env) ⇒ Object

Explicit routing, NO framework: ~10 routes in a case. A single rescue centralizes the error->status mapping. Only SYNCHRONOUS errors (before the fiber) become HTTP status; a task failure travels as an event on the stream and lands in GET /v1/tasks/:id.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/insika/server/app.rb', line 87

def call(env)
  req = Rack::Request.new(env)
  route(req)
rescue JSON::ParserError => e
  error_response(400, e) # malformed JSON, before any dispatch
rescue Insika::ValidationError => e
  error_response(422, e)
rescue Insika::NotFoundError => e
  error_response(404, e)
rescue Async::TimeoutError => e
  error_response(504, e) # synchronous control request exceeded the ceiling
rescue StandardError => e
  error_response(500, e)
end