insika 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +361 -0
- data/LICENSE +21 -0
- data/README.md +136 -2
- data/bin/insika +366 -0
- data/docs/AGENTS.md +618 -0
- data/docs/ARCHITECTURE.md +333 -0
- data/docs/BENCHMARK.md +114 -0
- data/docs/CHANNELS.md +453 -0
- data/docs/CONTEXT.md +117 -0
- data/docs/DEPLOY.md +354 -0
- data/docs/EMBEDDING.md +198 -0
- data/docs/EVALS.md +273 -0
- data/docs/LOADTEST.md +232 -0
- data/docs/OBSERVABILITY.md +374 -0
- data/docs/PLUGINS.md +211 -0
- data/docs/REFINEMENT.md +477 -0
- data/docs/RELEASING.md +70 -0
- data/docs/RUNNING-LOCAL.md +153 -0
- data/docs/SANDBOX.md +114 -0
- data/docs/SECURITY.md +375 -0
- data/docs/SKILLS.md +284 -0
- data/docs/TOOLS.md +302 -0
- data/docs/WHY.md +137 -0
- data/docs/WORKFLOWS.md +225 -0
- data/docs/build.md +14 -0
- data/docs/index.md +68 -0
- data/docs/onboarding/start.md +126 -0
- data/docs/operate.md +12 -0
- data/docs/ship.md +10 -0
- data/docs/understand.md +10 -0
- data/lib/insika/agent_file_store.rb +125 -0
- data/lib/insika/agent_profile.rb +255 -0
- data/lib/insika/alert_dispatcher.rb +139 -0
- data/lib/insika/allowlist.rb +28 -0
- data/lib/insika/baseline_store.rb +74 -0
- data/lib/insika/budget_ledger.rb +135 -0
- data/lib/insika/capability/resolved_tool.rb +34 -0
- data/lib/insika/capability_registry.rb +112 -0
- data/lib/insika/channel_delivery.rb +153 -0
- data/lib/insika/channel_registry.rb +30 -0
- data/lib/insika/channels/relay.rb +178 -0
- data/lib/insika/channels/web/widget.js +283 -0
- data/lib/insika/channels/web.rb +211 -0
- data/lib/insika/channels/webhook.rb +58 -0
- data/lib/insika/chat_builder.rb +303 -0
- data/lib/insika/checkpoint.rb +13 -0
- data/lib/insika/checkpoint_store.rb +153 -0
- data/lib/insika/circuit_state.rb +114 -0
- data/lib/insika/coercion.rb +58 -0
- data/lib/insika/command.rb +32 -0
- data/lib/insika/command_bus.rb +39 -0
- data/lib/insika/commands/agent_payload.rb +43 -0
- data/lib/insika/commands/approve_action.rb +46 -0
- data/lib/insika/commands/cancel_task.rb +33 -0
- data/lib/insika/commands/create_agent.rb +54 -0
- data/lib/insika/commands/create_session.rb +67 -0
- data/lib/insika/commands/delete_agent.rb +33 -0
- data/lib/insika/commands/delete_agent_file.rb +50 -0
- data/lib/insika/commands/delete_data_tool.rb +33 -0
- data/lib/insika/commands/delete_llm_provider.rb +36 -0
- data/lib/insika/commands/delete_mcp.rb +30 -0
- data/lib/insika/commands/delete_skill.rb +43 -0
- data/lib/insika/commands/delete_system_file.rb +29 -0
- data/lib/insika/commands/gate_refinement.rb +245 -0
- data/lib/insika/commands/import_mcp_tools.rb +48 -0
- data/lib/insika/commands/import_tools.rb +81 -0
- data/lib/insika/commands/issue_tenant_token.rb +41 -0
- data/lib/insika/commands/memory_add_note.rb +32 -0
- data/lib/insika/commands/memory_forget_fact.rb +32 -0
- data/lib/insika/commands/memory_put_fact.rb +35 -0
- data/lib/insika/commands/pause_task.rb +29 -0
- data/lib/insika/commands/resolve_refinement.rb +126 -0
- data/lib/insika/commands/restore_agent_file.rb +36 -0
- data/lib/insika/commands/restore_data_tool.rb +34 -0
- data/lib/insika/commands/restore_system_file.rb +31 -0
- data/lib/insika/commands/resume_task.rb +85 -0
- data/lib/insika/commands/revoke_token.rb +39 -0
- data/lib/insika/commands/rotate_tenant_token.rb +43 -0
- data/lib/insika/commands/run_refinement.rb +133 -0
- data/lib/insika/commands/send_message.rb +150 -0
- data/lib/insika/commands/set_agent_tools.rb +39 -0
- data/lib/insika/commands/set_skill_agents.rb +112 -0
- data/lib/insika/commands/trigger_workflow.rb +80 -0
- data/lib/insika/commands/update_agent.rb +49 -0
- data/lib/insika/commands/update_settings.rb +33 -0
- data/lib/insika/commands/upsert_llm_provider.rb +34 -0
- data/lib/insika/commands/upsert_mcp.rb +32 -0
- data/lib/insika/commands/write_agent_file.rb +57 -0
- data/lib/insika/commands/write_data_tool.rb +43 -0
- data/lib/insika/commands/write_golden.rb +58 -0
- data/lib/insika/commands/write_skill.rb +60 -0
- data/lib/insika/commands/write_system_file.rb +31 -0
- data/lib/insika/config_store.rb +89 -0
- data/lib/insika/context/builder.rb +166 -0
- data/lib/insika/context/catalog_provider.rb +23 -0
- data/lib/insika/context/fragment.rb +43 -0
- data/lib/insika/context/priority.rb +30 -0
- data/lib/insika/context/provider.rb +19 -0
- data/lib/insika/context/providers/memory.rb +60 -0
- data/lib/insika/context/providers/prompt.rb +105 -0
- data/lib/insika/context/providers/request.rb +32 -0
- data/lib/insika/context/providers/session.rb +123 -0
- data/lib/insika/context/providers/skill.rb +24 -0
- data/lib/insika/context/providers/skill_trigger.rb +128 -0
- data/lib/insika/context/providers/tool_search.rb +20 -0
- data/lib/insika/context_trace_store.rb +92 -0
- data/lib/insika/delegation_store.rb +153 -0
- data/lib/insika/doctor.rb +539 -0
- data/lib/insika/dsl/definition.rb +55 -0
- data/lib/insika/dsl/runtime.rb +382 -0
- data/lib/insika/dsl/server_boot.rb +98 -0
- data/lib/insika/dsl/system.rb +93 -0
- data/lib/insika/dsl/workflow_adapter.rb +59 -0
- data/lib/insika/dsl.rb +364 -0
- data/lib/insika/edge_limiter.rb +268 -0
- data/lib/insika/egress_guard.rb +75 -0
- data/lib/insika/env_schema.rb +249 -0
- data/lib/insika/errors.rb +201 -0
- data/lib/insika/evals/assertions.rb +247 -0
- data/lib/insika/evals/baseline.rb +69 -0
- data/lib/insika/evals/golden.rb +172 -0
- data/lib/insika/evals/judge.rb +225 -0
- data/lib/insika/evals/pairwise.rb +178 -0
- data/lib/insika/evals/report.rb +115 -0
- data/lib/insika/evals/runner.rb +141 -0
- data/lib/insika/evals/transport.rb +178 -0
- data/lib/insika/event.rb +18 -0
- data/lib/insika/event_stream.rb +132 -0
- data/lib/insika/executor.rb +1995 -0
- data/lib/insika/frontmatter.rb +42 -0
- data/lib/insika/golden_store.rb +145 -0
- data/lib/insika/hooks.rb +48 -0
- data/lib/insika/http_client.rb +63 -0
- data/lib/insika/inbound_log.rb +84 -0
- data/lib/insika/llm_configurator.rb +99 -0
- data/lib/insika/llm_provider_store.rb +83 -0
- data/lib/insika/loop_detector.rb +143 -0
- data/lib/insika/mcp_http_client.rb +67 -0
- data/lib/insika/mcp_store.rb +115 -0
- data/lib/insika/mcp_tool_ingestor.rb +143 -0
- data/lib/insika/memory_store.rb +93 -0
- data/lib/insika/message_origin.rb +76 -0
- data/lib/insika/middleware.rb +36 -0
- data/lib/insika/model_policy.rb +52 -0
- data/lib/insika/model_resolver.rb +176 -0
- data/lib/insika/model_selection.rb +115 -0
- data/lib/insika/onboarding.rb +208 -0
- data/lib/insika/outbox_store.rb +166 -0
- data/lib/insika/overlay_tool_registry.rb +102 -0
- data/lib/insika/pack.rb +102 -0
- data/lib/insika/pack_importer.rb +123 -0
- data/lib/insika/pending_action_store.rb +120 -0
- data/lib/insika/plugin/loader.rb +356 -0
- data/lib/insika/plugin.rb +35 -0
- data/lib/insika/policy/engine.rb +83 -0
- data/lib/insika/policy/policy.rb +120 -0
- data/lib/insika/policy_registry.rb +23 -0
- data/lib/insika/profile_source.rb +143 -0
- data/lib/insika/prompt_catalog.rb +61 -0
- data/lib/insika/provider_error_classifier.rb +160 -0
- data/lib/insika/queue_policy.rb +167 -0
- data/lib/insika/recovery.rb +168 -0
- data/lib/insika/refinement/candidate.rb +159 -0
- data/lib/insika/refinement/evidence_collector.rb +371 -0
- data/lib/insika/refinement/gate.rb +234 -0
- data/lib/insika/refinement/panel.rb +222 -0
- data/lib/insika/refinement/proposer.rb +262 -0
- data/lib/insika/refinement_store.rb +295 -0
- data/lib/insika/registry.rb +59 -0
- data/lib/insika/reliability.rb +185 -0
- data/lib/insika/safety/config.rb +109 -0
- data/lib/insika/safety/detectors.rb +176 -0
- data/lib/insika/safety/factory.rb +102 -0
- data/lib/insika/safety/input_guardrail.rb +102 -0
- data/lib/insika/safety/moderator.rb +94 -0
- data/lib/insika/safety/output_filter.rb +79 -0
- data/lib/insika/safety/output_validator.rb +101 -0
- data/lib/insika/safety/safe_responses.rb +47 -0
- data/lib/insika/sandbox/boundary.rb +93 -0
- data/lib/insika/sandbox/docker.rb +74 -0
- data/lib/insika/sandbox/local.rb +33 -0
- data/lib/insika/sandbox/runner.rb +80 -0
- data/lib/insika/sandbox.rb +85 -0
- data/lib/insika/schema_guard.rb +147 -0
- data/lib/insika/secret_masking.rb +34 -0
- data/lib/insika/server/a2a/agent_card.rb +27 -0
- data/lib/insika/server/a2a/app.rb +112 -0
- data/lib/insika/server/a2a/client.rb +101 -0
- data/lib/insika/server/a2a/errors.rb +32 -0
- data/lib/insika/server/a2a/http.rb +42 -0
- data/lib/insika/server/a2a/message.rb +27 -0
- data/lib/insika/server/a2a/protocol.rb +45 -0
- data/lib/insika/server/a2a/remotes.rb +25 -0
- data/lib/insika/server/a2a/task_projection.rb +40 -0
- data/lib/insika/server/app.rb +1022 -0
- data/lib/insika/server/boot.rb +119 -0
- data/lib/insika/server/rack_app.rb +118 -0
- data/lib/insika/server/responses.rb +165 -0
- data/lib/insika/server/sse_body.rb +96 -0
- data/lib/insika/server/tenant_auth.rb +61 -0
- data/lib/insika/session_actor.rb +162 -0
- data/lib/insika/session_store.rb +143 -0
- data/lib/insika/settings_store.rb +154 -0
- data/lib/insika/shutdown.rb +125 -0
- data/lib/insika/skill_catalog.rb +220 -0
- data/lib/insika/skill_store.rb +127 -0
- data/lib/insika/steer_injector.rb +110 -0
- data/lib/insika/store.rb +52 -0
- data/lib/insika/stores/memory.rb +123 -0
- data/lib/insika/stores/sqlite.rb +183 -0
- data/lib/insika/studio/app.rb +1693 -0
- data/lib/insika/studio/assets/dist/application.css +1 -0
- data/lib/insika/studio/assets/dist/application.js +70 -0
- data/lib/insika/studio/forms.rb +335 -0
- data/lib/insika/studio/nav_icons.rb +31 -0
- data/lib/insika/studio/views/_message.erb +44 -0
- data/lib/insika/studio/views/agent_detail.erb +285 -0
- data/lib/insika/studio/views/agents.erb +63 -0
- data/lib/insika/studio/views/approvals.erb +41 -0
- data/lib/insika/studio/views/chats.erb +34 -0
- data/lib/insika/studio/views/evals.erb +83 -0
- data/lib/insika/studio/views/home.erb +72 -0
- data/lib/insika/studio/views/layout.erb +94 -0
- data/lib/insika/studio/views/login.erb +17 -0
- data/lib/insika/studio/views/mcp.erb +91 -0
- data/lib/insika/studio/views/not_found.erb +5 -0
- data/lib/insika/studio/views/playground.erb +47 -0
- data/lib/insika/studio/views/refinement.erb +234 -0
- data/lib/insika/studio/views/session.erb +137 -0
- data/lib/insika/studio/views/settings.erb +168 -0
- data/lib/insika/studio/views/skills.erb +141 -0
- data/lib/insika/studio/views/system_files.erb +65 -0
- data/lib/insika/studio/views/task.erb +105 -0
- data/lib/insika/studio/views/tasks.erb +33 -0
- data/lib/insika/studio/views/tool_edit.erb +107 -0
- data/lib/insika/studio/views/tools.erb +89 -0
- data/lib/insika/subagent_graph.rb +96 -0
- data/lib/insika/system_file_store.rb +96 -0
- data/lib/insika/task_actor.rb +128 -0
- data/lib/insika/task_store.rb +250 -0
- data/lib/insika/telemetry/pricing.rb +104 -0
- data/lib/insika/telemetry/recorder.rb +228 -0
- data/lib/insika/telemetry.rb +127 -0
- data/lib/insika/testing/store_contract.rb +270 -0
- data/lib/insika/tick.rb +122 -0
- data/lib/insika/token_estimator.rb +16 -0
- data/lib/insika/token_store.rb +168 -0
- data/lib/insika/tool_assembly.rb +140 -0
- data/lib/insika/tool_catalog.rb +89 -0
- data/lib/insika/tool_definition.rb +518 -0
- data/lib/insika/tool_envelope.rb +140 -0
- data/lib/insika/tool_manifest.rb +218 -0
- data/lib/insika/tool_output_compressor.rb +100 -0
- data/lib/insika/tool_registry.rb +21 -0
- data/lib/insika/tool_store.rb +135 -0
- data/lib/insika/tool_trace_store.rb +92 -0
- data/lib/insika/tools/a2a_remote.rb +48 -0
- data/lib/insika/tools/agent_enum.rb +68 -0
- data/lib/insika/tools/concurrency.rb +54 -0
- data/lib/insika/tools/data_defined_tool.rb +219 -0
- data/lib/insika/tools/load_skill.rb +99 -0
- data/lib/insika/tools/remember.rb +53 -0
- data/lib/insika/tools/stuck_signal.rb +44 -0
- data/lib/insika/tools/subagent.rb +75 -0
- data/lib/insika/tools/subagents.rb +77 -0
- data/lib/insika/tools/tool_search.rb +94 -0
- data/lib/insika/turn_output.rb +139 -0
- data/lib/insika/turn_state.rb +162 -0
- data/lib/insika/turn_timing.rb +56 -0
- data/lib/insika/usage_ledger.rb +47 -0
- data/lib/insika/version.rb +3 -1
- data/lib/insika/wiring/graph.rb +249 -0
- data/lib/insika/workflow.rb +185 -0
- data/lib/insika/workflow_registry.rb +33 -0
- data/lib/insika.rb +220 -4
- metadata +412 -8
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
require "async/semaphore"
|
|
6
|
+
|
|
7
|
+
module Insika
|
|
8
|
+
module Stores
|
|
9
|
+
# SQLite backend — the production default.
|
|
10
|
+
# A single kv table; the domain lives in the scopes. One handle per process,
|
|
11
|
+
# writes in a transaction serialized by an Async::Semaphore.
|
|
12
|
+
class SQLite
|
|
13
|
+
include Store
|
|
14
|
+
|
|
15
|
+
DDL = <<~SQL
|
|
16
|
+
CREATE TABLE IF NOT EXISTS kv (
|
|
17
|
+
scope TEXT NOT NULL,
|
|
18
|
+
key TEXT NOT NULL,
|
|
19
|
+
value TEXT NOT NULL,
|
|
20
|
+
updated_at TEXT NOT NULL,
|
|
21
|
+
PRIMARY KEY (scope, key)
|
|
22
|
+
) WITHOUT ROWID;
|
|
23
|
+
SQL
|
|
24
|
+
|
|
25
|
+
# JSON model types + Symbol (coerced to String on write).
|
|
26
|
+
# Any other type is "garbage" and must be rejected. Identical to
|
|
27
|
+
# Stores::Memory — both backends share the SAME type model
|
|
28
|
+
# (the contract suite is honest).
|
|
29
|
+
JSONABLE = [NilClass, TrueClass, FalseClass, Integer, Float,
|
|
30
|
+
String, Symbol].freeze
|
|
31
|
+
private_constant :JSONABLE
|
|
32
|
+
|
|
33
|
+
# lazy require: the core installs without the sqlite3 gem
|
|
34
|
+
# when only Memory is used.
|
|
35
|
+
def initialize(path:, serializer: JSON)
|
|
36
|
+
require "sqlite3"
|
|
37
|
+
|
|
38
|
+
@serializer = serializer
|
|
39
|
+
@db = SQLite3::Database.new(path)
|
|
40
|
+
@write_semaphore = Async::Semaphore.new(1)
|
|
41
|
+
@tx_owner = nil
|
|
42
|
+
|
|
43
|
+
# Multi-process boot (N Falcon workers opening the SAME file at the
|
|
44
|
+
# same time): `PRAGMA journal_mode = WAL` on a new file needs an
|
|
45
|
+
# EXCLUSIVE lock and may return SQLITE_BUSY right then — the busy
|
|
46
|
+
# timeout alone does NOT cover the journal-mode switch. Hence: timeout
|
|
47
|
+
# FIRST (covers the DDL and the hot path) + retry with backoff around
|
|
48
|
+
# the initialization (covers the WAL-switch race). Idempotent:
|
|
49
|
+
# reopening already-in-WAL is a no-op.
|
|
50
|
+
#
|
|
51
|
+
# `busy_handler_timeout=`, NOT `busy_timeout=`: the C-level handler
|
|
52
|
+
# sleeps holding the GVL, so a worker waiting on another PROCESS's
|
|
53
|
+
# write lock would stall every fiber it is running for up to the full
|
|
54
|
+
# timeout (measured: a waiter blocks the winner's own commit). The
|
|
55
|
+
# Ruby-level handler sleeps in Ruby — the scheduler keeps the rest of
|
|
56
|
+
# the worker breathing while this handle waits its turn.
|
|
57
|
+
@db.busy_handler_timeout = 5_000
|
|
58
|
+
with_busy_retry do
|
|
59
|
+
@db.execute("PRAGMA journal_mode = WAL")
|
|
60
|
+
@db.execute("PRAGMA synchronous = NORMAL")
|
|
61
|
+
@db.execute_batch(DDL)
|
|
62
|
+
end
|
|
63
|
+
# The WITHOUT ROWID PRIMARY KEY (scope, key) is already the prefix index —
|
|
64
|
+
# there is no extra index to create.
|
|
65
|
+
rescue ::SQLite3::Exception => e
|
|
66
|
+
raise Insika::StoreError, "failed to open #{path}: #{e.message}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Short retry for SQLITE_BUSY at INITIALIZATION (the WAL-switch race at
|
|
70
|
+
# multi-process boot). ~10 attempts × 60ms ≈ 0.6s worst case — enough
|
|
71
|
+
# for the winning process to finish the journal-mode switch. Only
|
|
72
|
+
# here; the hot path uses `busy_timeout` + the in-process semaphore.
|
|
73
|
+
def with_busy_retry(attempts: 10, backoff: 0.06)
|
|
74
|
+
tries = 0
|
|
75
|
+
begin
|
|
76
|
+
yield
|
|
77
|
+
rescue ::SQLite3::Exception => e
|
|
78
|
+
raise unless e.message =~ /lock|busy/i
|
|
79
|
+
|
|
80
|
+
tries += 1
|
|
81
|
+
raise if tries >= attempts
|
|
82
|
+
|
|
83
|
+
sleep(backoff)
|
|
84
|
+
retry
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
private :with_busy_retry
|
|
88
|
+
|
|
89
|
+
def close
|
|
90
|
+
@db&.close
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def get(scope, key)
|
|
95
|
+
row = @db.get_first_value(
|
|
96
|
+
"SELECT value FROM kv WHERE scope = ? AND key = ?", [scope, key]
|
|
97
|
+
)
|
|
98
|
+
row.nil? ? nil : @serializer.parse(row)
|
|
99
|
+
rescue ::SQLite3::Exception => e
|
|
100
|
+
raise Insika::StoreError, e.message
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def set(scope, key, value)
|
|
104
|
+
serialized = serialize(value) # fail-fast BEFORE writing
|
|
105
|
+
transaction do
|
|
106
|
+
@db.execute(
|
|
107
|
+
"INSERT OR REPLACE INTO kv (scope, key, value, updated_at) " \
|
|
108
|
+
"VALUES (?, ?, ?, ?)",
|
|
109
|
+
[scope, key, serialized, Time.now.utc.iso8601]
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
value
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def delete(scope, key)
|
|
116
|
+
transaction do
|
|
117
|
+
@db.execute("DELETE FROM kv WHERE scope = ? AND key = ?", [scope, key])
|
|
118
|
+
@db.changes.positive?
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def list(scope, prefix = nil)
|
|
123
|
+
keys = @db.execute(
|
|
124
|
+
"SELECT key FROM kv WHERE scope = ? ORDER BY key", [scope]
|
|
125
|
+
).map(&:first)
|
|
126
|
+
prefix ? keys.select { |k| k.start_with?(prefix) } : keys
|
|
127
|
+
rescue ::SQLite3::Exception => e
|
|
128
|
+
raise Insika::StoreError, e.message
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# BEGIN IMMEDIATE ... COMMIT/ROLLBACK, serialized by the semaphore.
|
|
132
|
+
# A nested one reuses the outer transaction.
|
|
133
|
+
def transaction(&blk)
|
|
134
|
+
return yield if @tx_owner == Fiber.current
|
|
135
|
+
|
|
136
|
+
@write_semaphore.acquire do
|
|
137
|
+
begin
|
|
138
|
+
@db.transaction(:immediate)
|
|
139
|
+
@tx_owner = Fiber.current
|
|
140
|
+
result = yield
|
|
141
|
+
@db.commit
|
|
142
|
+
result
|
|
143
|
+
rescue StandardError
|
|
144
|
+
@db.rollback if @db.transaction_active?
|
|
145
|
+
raise
|
|
146
|
+
ensure
|
|
147
|
+
@tx_owner = nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
rescue ::SQLite3::Exception => e
|
|
151
|
+
raise Insika::StoreError, e.message
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
# Enforces the contract's type model at the boundary:
|
|
157
|
+
# Symbol/symbol-key become String; a type outside the JSON model ->
|
|
158
|
+
# StoreError on WRITE (fail-fast; never writes garbage).
|
|
159
|
+
#
|
|
160
|
+
# Does NOT use `generate(strict: true)`: under json 2.7.1 (the version that
|
|
161
|
+
# ships with ruby 3.3.5 pinned in Gemfile.lock) `strict` REJECTS Symbol,
|
|
162
|
+
# which would violate the Symbol coercion. The explicit validation
|
|
163
|
+
# is independent of the json version and gives the SAME semantics as Memory.
|
|
164
|
+
def serialize(value)
|
|
165
|
+
ensure_jsonable!(value)
|
|
166
|
+
@serializer.generate(value)
|
|
167
|
+
rescue JSON::GeneratorError => e
|
|
168
|
+
raise Insika::StoreError, "value not serializable: #{e.message}"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def ensure_jsonable!(value)
|
|
172
|
+
case value
|
|
173
|
+
when *JSONABLE then nil
|
|
174
|
+
when Array then value.each { |v| ensure_jsonable!(v) }
|
|
175
|
+
when Hash then value.each { |k, v| ensure_jsonable!(k); ensure_jsonable!(v) }
|
|
176
|
+
else
|
|
177
|
+
raise Insika::StoreError,
|
|
178
|
+
"value not serializable: #{value.class} not allowed in JSON"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|