Class: Insika::Server::AppBuilder

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

Overview

Assembles Server::App from a graph. Also answers the two questions the boot banner asks (workflows?/channels?), so registering the env channels happens exactly once and in one place.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handle, token: nil, **config) ⇒ AppBuilder

Fixed local token: gates /v1 (Bearer) — and, under serve, logs into the Studio (cookie). Never a real secret; override with token:/ADMIN_TOKEN.



37
38
39
40
41
42
# File 'lib/insika/server/rack_app.rb', line 37

def initialize(handle, token: nil, **config)
  @rt = handle.respond_to?(:runtime) ? handle.runtime : handle
  @graph = @rt.graph
  @token = token || ENV.fetch("ADMIN_TOKEN", "local-demo")
  @config = config
end

Instance Attribute Details

#criterionObject (readonly)

the frozen criterion, memoized when the relay runs in shadow — nil otherwise. Loaded ONCE at boot (the refusal below), then shared.



48
49
50
# File 'lib/insika/server/rack_app.rb', line 48

def criterion
  @criterion
end

#graphObject (readonly)

Returns the value of attribute graph.



45
46
47
# File 'lib/insika/server/rack_app.rb', line 45

def graph
  @graph
end

#tokenObject (readonly)

Returns the value of attribute token.



44
45
46
# File 'lib/insika/server/rack_app.rb', line 44

def token
  @token
end

Instance Method Details

#appObject



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
82
# File 'lib/insika/server/rack_app.rb', line 50

def app
  tenancy = @config[:tenancy] || ENV["INSIKA_TENANCY"] || "single_tenant"
  # WS1: the token store is handed over ONLY in multi_tenant mode — in
  # single_tenant the classic gateway token is the only credential
  # (passing the store would silently widen the surface).
  store = tenancy == "multi_tenant" ? @graph.token_store : nil
  @app ||= Insika::Server::App.new(
    command_bus: @graph.bus, event_stream: @graph.event_stream,
    session_store: @graph.session_store, task_store: @graph.task_store,
    pending_action_store: @graph.pending_action_store,
    provisioner: Insika::PackImporter.new(bus: @graph.bus, profiles: @graph.profiles),
    # GET /v1/agents/:id — the read-only capability view a case's `requires`
    # resolves against.
    profiles: @graph.profiles,
    # the OSS onboarding surface (start.md + models.json + docs).
    # This is the primary "build my first agent" target — models.json reports the
    # DSL's stores + the agents this process serves (each id IS the `model`).
    onboarding: build_onboarding,
    # GET /v1/workflows + POST /v1/workflows/:name, opt-in by
    # injection like every other edge — nil when the system declares none,
    # so the routes simply do not exist (404, parity).
    workflow_registry: (@graph.workflow_registry if workflows?),
    # the bundled relay, when the env turns it on. Same rule as the
    # OTEL bridge — a feature only `config.ru` can reach is a feature the
    # docs are half-true about.
    channels: (@graph.channel_registry if channels?),
    config: { gateway_token: @token, tenancy: tenancy }.merge(@config),
    token_store: store,
    outcome_store: @graph.outcome_store,
    # a 500's error_ref must be findable in the process log.
    logger: $stdout
  )
end

#channels?Boolean

Registers the env-configured channels once, and reports whether any exist.

Returns:

  • (Boolean)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/insika/server/rack_app.rb', line 87

def channels?
  unless defined?(@channels_ready)
    @channels_ready = true
    relay = Insika::Channels::Relay.from_env(
      http: Insika::HttpClient.new,
      allow_http: Insika::EnvSchema.truthy?(ENV["INSIKA_EGRESS_ALLOW_HTTP"]),
      allow_private: Insika::EnvSchema.truthy?(ENV["INSIKA_EGRESS_ALLOW_PRIVATE"])
    )
    # NO criterion, no shadow — enforced at boot, not per
    # turn. Raises ConfigError when the file is missing, unparseable, or
    # incomplete; the criterion is memoized here and injected into
    # ChannelDelivery (its sha stamps every pair our half records).
    if relay&.shadow?
      criterion_path = Insika::EnvSchema.read("INSIKA_PARITY_CRITERION")
      raise Insika::ConfigError, "shadow mode requires INSIKA_PARITY_CRITERION (the frozen criterion)" if criterion_path.nil?

      @criterion = Insika::Parity::Criterion.load(criterion_path)
      @graph.channel_delivery&.criterion_sha = @criterion.sha
    end
    @graph.channel_registry.register(relay.id, relay) if relay

    widget = Insika::Channels::Web.from_env(
      chat_rate_limit: Insika::Channels::Web.limit_resolver(
        profiles: @graph.profiles, settings_store: @rt.component(:settings_store)
      )
    )
    @graph.channel_registry.register(widget.id, widget) if widget
  end
  !@graph.channel_registry.names.empty?
end

#workflows?Boolean

Returns:

  • (Boolean)


84
# File 'lib/insika/server/rack_app.rb', line 84

def workflows? = !@graph.workflow_registry.names.empty?