k9o 1.2.0 → 1.4.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.
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # k9o
2
2
 
3
- The Kilimanjaro platform SDK for Ruby.
4
-
5
- Kilimanjaro is an AI-native platform on a Governed Database. Its products are Keep (AI Security and Governance), Pulse (Data Intelligence), Gate (Customer Identity and Access Management) and Kibo (Keys and Secrets Management).
3
+ Client for Kilimanjaro. Read and write your data, store files, manage policies and migrations,
4
+ deploy and invoke agents or functions, and publish to and subscribe to streams and realtime events.
6
5
 
7
6
  ## Contents
8
7
 
@@ -11,8 +10,15 @@ Kilimanjaro is an AI-native platform on a Governed Database. Its products are Ke
11
10
  - [Quick start](#quick-start)
12
11
  - [Authentication and project context](#authentication-and-project-context)
13
12
  - [Running queries](#running-queries)
13
+ - [Query with SQL](#query-with-sql)
14
+ - [Search](#search)
15
+ - [Streams](#streams)
16
+ - [Approvals](#approvals)
17
+ - [Approval policies](#approval-policies)
18
+ - [Schema collections](#schema-collections)
14
19
  - [Handling errors](#handling-errors)
15
20
  - [Appendix](#appendix)
21
+ - [About Kilimanjaro](#about-kilimanjaro)
16
22
  - [License](#license)
17
23
 
18
24
  ## Links
@@ -25,13 +31,13 @@ Kilimanjaro is an AI-native platform on a Governed Database. Its products are Ke
25
31
  ## Install
26
32
 
27
33
  ```bash
28
- gem install k9o
34
+ gem install k9o -v 1.4.0
29
35
  ```
30
36
 
31
37
  Or add it to your `Gemfile`:
32
38
 
33
39
  ```ruby
34
- gem "k9o"
40
+ gem "k9o", "1.4.0"
35
41
  ```
36
42
 
37
43
  Requires Ruby 3.2 or newer. See [requirements](#requirements) for details.
@@ -57,11 +63,11 @@ k9o = K9o.create_client(
57
63
  ```ruby
58
64
  new_task = { "title" => "Write the README", "done" => false }
59
65
  created = k9o.from("tasks").insert(new_task)
60
- puts "created: #{created.data['title']}"
66
+ puts "Output: #{created.data['title']}"
61
67
  ```
62
68
 
63
69
  ```text
64
- created: Write the README
70
+ Output: Write the README
65
71
  ```
66
72
 
67
73
  ### List tasks
@@ -69,12 +75,12 @@ created: Write the README
69
75
  ```ruby
70
76
  response = k9o.from("tasks").select.eq("done", false).limit(10).execute
71
77
  response.data.each do |task|
72
- puts "#{task['title']} #{task['done']}"
78
+ puts "Output: #{task['title']} #{task['done']}"
73
79
  end
74
80
  ```
75
81
 
76
82
  ```text
77
- Write the README false
83
+ Output: Write the README false
78
84
  ```
79
85
 
80
86
  ### Update and delete a task
@@ -97,27 +103,28 @@ The client needs a key and, for project calls, an org and project.
97
103
  | Option | What it does |
98
104
  |---|---|
99
105
  | `api_key:` | Bearer token sent with every request. |
100
- | `org_slug:` | Organization. On the default host the SDK uses `https://<org_slug>.api.k9o.io`. |
106
+ | `org_slug:` | Organization. The SDK builds `https://<org_slug>.<host>` from the platform host. |
101
107
  | `project_slug:` | Project. Required by every project-scoped namespace. |
102
108
 
103
109
  See [client options](#client-options) for every option.
104
110
 
105
111
  ### Choose a host
106
112
 
107
- Pick one of two forms.
113
+ The API host always comes from the base URL. Pick one of two forms.
108
114
 
109
- - Default platform host: pass the org slug, project slug and key.
110
- - Custom or self-hosted host: pass `base_url`, project slug and key, and no org slug.
115
+ - Default public host: pass the org slug, project slug and key.
116
+ - Any other platform, internal or air-gapped host: pass its `base_url` as well.
111
117
 
112
118
  ```ruby
113
119
  k9o = K9o.create_client(
114
- base_url: "https://k9o.internal.example.com",
120
+ base_url: "https://api.corp.internal",
121
+ org_slug: "acme",
115
122
  project_slug: "task-app",
116
123
  api_key: ENV.fetch("K9O_API_KEY")
117
124
  )
118
125
  ```
119
126
 
120
- The base URL rules are listed under [regional hosts](#regional-hosts).
127
+ This client talks to `https://acme.api.corp.internal`. See [regional hosts](#regional-hosts) for the rules.
121
128
 
122
129
  ## Running queries
123
130
 
@@ -136,13 +143,193 @@ A sub-object the server did not send is `nil`, so use `&.` when it may be absent
136
143
  ```ruby
137
144
  request_id = response.meta.context.request_id
138
145
  total = response.meta.cursor.total_count
139
- puts "#{request_id} #{total}"
146
+ puts "Output: #{request_id} #{total}"
147
+ ```
148
+
149
+ ```text
150
+ Output: req_01H... 1
151
+ ```
152
+
153
+ ## Query with SQL
154
+
155
+ `k9o.query(sql, params)` runs one read-only SQL statement and returns the rows in `data`.
156
+ It is read-only (a write is refused with `K9O_RAW_SQL_READ_ONLY`; change data through `from`), takes one statement per call,
157
+ applies row-level security, accepts at most 100000 characters of SQL and 1000 parameters, and needs the project editor role.
158
+ A reference to `dataset.<name>` reads an analytics dataset.
159
+
160
+ Pass an array to bind `$1`, `$2`, and so on, or a hash to bind `:name` placeholders. Values are bound by the database and can be a string, number, boolean or `nil`; serialise anything else (for example as JSON text) first.
161
+
162
+ ```ruby
163
+ rows = k9o.query("SELECT data->>'title' AS title FROM posts WHERE data->>'status' = :status", status: "published")
164
+ puts "Output: #{rows.data.first['title']}"
165
+
166
+ same = k9o.query("SELECT count(*) AS n FROM posts WHERE data->>'status' = $1", ["published"])
167
+ puts "Output: #{same.data.first['n']}"
140
168
  ```
141
169
 
142
170
  ```text
143
- req_01H... 1
171
+ Output: Hello
172
+ Output: 12
173
+ ```
174
+
175
+ A `:name` with no key in the hash raises `K9o::KError` with code `K9O_INVALID_FIELD_VALUE` before any request is sent.
176
+
177
+ ## Search
178
+
179
+ `k9o.search(collection, column:, q:)` runs a full-text search over one text column, best matches first.
180
+ `language:` defaults to `english`; `type:` is `plain` (default), `phrase` or `websearch`. `limit:` and `cursor:` page the result.
181
+
182
+ ```ruby
183
+ found = k9o.search("articles", column: "body", q: "climate resilience", limit: 5)
184
+ puts "Output: #{found.data.length} #{found.data.first['title']}"
185
+ ```
186
+
187
+ ```text
188
+ Output: 5 Adapting to heat
189
+ ```
190
+
191
+ The same search is available on a query as `text_search(column, query, language: "english", type: "plain")`.
192
+ `vector_search(column, embedding, metric: "cosine", limit: nil)` finds the closest rows to an embedding, closest first;
193
+ `metric:` is `cosine`, `euclidean` or `dot`, and `limit:` is 1 to 1000.
194
+
195
+ ```ruby
196
+ vector = k9o.ai.embed(input: "heat waves").data["embeddings"].first
197
+ near = k9o.from("articles").select.vector_search("embedding", vector, limit: 3).execute
198
+ puts "Output: #{near.data.length}"
199
+ ```
200
+
201
+ ```text
202
+ Output: 3
203
+ ```
204
+
205
+ ## Streams
206
+
207
+ Publish events to a stream and tail them live. Publishing needs the project admin role; subscribing and replaying need admin or auditor.
208
+ `subscribe` reconnects from the last sequence when the server closes the connection, so no event is repeated or skipped.
209
+
210
+ ```ruby
211
+ stream = k9o.streams.create("Orders feed", 1_073_741_824)
212
+ slug = stream.data["slug"]
213
+ puts "Output: #{slug}"
214
+ k9o.streams.publish(slug, { "order" => 1001 }, subject: "eu.new")
215
+
216
+ k9o.streams.subscribe(slug).each do |event|
217
+ puts "Output: #{event["sequence"]} #{event["data"]["order"]}"
218
+ break
219
+ end
220
+ ```
221
+
222
+ ```text
223
+ Output: orders-feed
224
+ Output: 1 1001
225
+ ```
226
+
227
+ `replay` reads a bounded range of past events.
228
+ Failures raise `K9o::KError`: `K9O_STREAM_UNAVAILABLE` while streams are not available, and `K9O_STREAM_INVALID` for a bad subject.
229
+
230
+ ## Approvals
231
+
232
+ Writes that need approval wait in a queue for a second person. `k9o.approvals` reads that queue and decides its changes.
233
+ Project admins and auditors see every change; other members see only the ones they proposed.
234
+
235
+ ```ruby
236
+ queue = k9o.approvals.list(status: "pending_approval", limit: 20)
237
+ change = queue.data.first
238
+
239
+ puts "Output: #{change["id"]} #{change["operation"]} #{change["collection"]}"
240
+
241
+ decided = k9o.approvals.approve(change["id"], reason: "checked the amount")
242
+
243
+ puts "Output: #{decided.data["status"]}"
244
+ ```
245
+
246
+ ```text
247
+ Output: apr_01j9x3 update orders
248
+ Output: redeemed
249
+ ```
250
+
251
+ - A change is `pending_approval`, `approved`, `rejected`, `expired` or `redeemed` (applied). An `expired` change can still read `pending_approval` until someone decides it.
252
+ - `approve` can answer 200 with `status` still `pending_approval`. That means your approval is recorded and more approvers are needed; the change is not applied yet.
253
+ - `reject(id, reason:)` needs a non-empty `reason`; the SDK checks it before sending.
254
+ - `list` is not paginated: it returns the newest `limit` changes (1 to 200, default 50). `get(id)` reads one.
255
+ - Deciding needs the queue-admin role (project admin or auditor).
256
+
257
+ Decide many at once with `bulk`. `criteria` is a predicate over each pending change (see [Approval policies](#approval-policies)):
258
+
259
+ ```ruby
260
+ run = k9o.approvals.bulk(
261
+ action: "reject",
262
+ criteria: { "eq" => ["change.table", "orders"] },
263
+ reason: "stale"
264
+ )
265
+
266
+ puts "Output: #{run.data["affectedCount"]} #{run.data["affectedIds"].join(",")}"
267
+ ```
268
+
269
+ ```text
270
+ Output: 2 apr_01j9x3,apr_01j9x4
271
+ ```
272
+
273
+ - A row that fails (a conflict, an apply error) is skipped silently and left out of `affectedIds`. Compare `affectedIds` with the set you intended.
274
+ - `reject` needs a `reason`; `criteria` must be a predicate hash. `attestationId` identifies the batch, even when nothing matched.
275
+ - It considers the newest 10000 pending changes. Bulk approve is not guaranteed to apply every matched row.
276
+
277
+ ## Approval policies
278
+
279
+ A policy decides what happens to writes on one collection that match a predicate: `queue` them for approval, or `auto_approve` them.
280
+ For a write the first matching policy, oldest first, decides. Policies are evaluated for writes sent with the `requireApproval=true` query parameter.
281
+
282
+ ```ruby
283
+ policy = k9o.approval_policies.create(
284
+ collection: "orders",
285
+ operation: "update",
286
+ action: "queue",
287
+ predicate: { "gt" => ["change.row.amount", 1000] }
288
+ )
289
+
290
+ puts "Output: #{policy.data["id"]} #{policy.data["action"]}"
291
+
292
+ removed = k9o.approval_policies.delete(policy.data["id"])
293
+
294
+ puts "Output: #{removed.data["id"]}"
144
295
  ```
145
296
 
297
+ ```text
298
+ Output: apo_01j9x5 queue
299
+ Output: apo_01j9x5
300
+ ```
301
+
302
+ - `operation` is `insert`, `update` or `delete`; `action` is `auto_approve` or `queue`.
303
+ - `collection` matches `^[a-zA-Z_][a-zA-Z0-9_]{0,62}$` and `predicate` is a non-null hash. The SDK checks both before sending.
304
+ - A predicate has one operator key: `and`, `or`, `not`, `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `in_cidr`, `not_in_cidr` or `contains`.
305
+ `and` and `or` take an array of predicates, `not` takes one, and a comparison takes `[left, right]`.
306
+ - Operands start with `maker.` or `change.` (for example `change.row.amount`, `change.table`, `change.operation`, `maker.id`).
307
+ - `list` returns all policies. Identical policies are not de-duplicated. Creating and deleting need project admin or author.
308
+
309
+ ## Schema collections
310
+
311
+ `k9o.schema.collections.list` lists the project's collections by name, with their size and last migration.
312
+
313
+ ```ruby
314
+ page = k9o.schema.collections.list(limit: 2)
315
+
316
+ page.data.each do |collection|
317
+ puts "Output: #{collection["name"]} #{collection["rowCount"]} rows, #{collection["columnCount"]} columns"
318
+ end
319
+ puts "Output: next #{page.meta.cursor.next}"
320
+ ```
321
+
322
+ ```text
323
+ Output: orders 120 rows, 8 columns
324
+ Output: tasks 14 rows, 4 columns
325
+ Output: next dGFza3M
326
+ ```
327
+
328
+ - Pass `meta.cursor.next` as `cursor` for the next page; it is `nil` on the last page. `limit` is 1 to 200 (default 50).
329
+ - A row is `name`, `rowCount`, `rowCountIsExact`, `columnCount`, `indexCount`, `lastMigrationSlug` and `lastMigrationAppliedAt`.
330
+ - `rowCount` is a live count that skips soft-deleted rows and ignores row-level security. Without an `org_id` claim on the token, `meta.warnings` says so.
331
+ - Project admins, auditors, editors, members and viewers can read it.
332
+
146
333
  ## Handling errors
147
334
 
148
335
  Failures raise `K9o::KError`, a subclass of `StandardError`. Branch on `code`.
@@ -151,14 +338,14 @@ Failures raise `K9o::KError`, a subclass of `StandardError`. Branch on `code`.
151
338
  begin
152
339
  k9o.from("missing").select.execute
153
340
  rescue K9o::KError => error
154
- puts "#{error.code} #{error.status} #{error.request_id}"
155
- puts error.message
341
+ puts "Output: #{error.code} #{error.status} #{error.request_id}"
342
+ puts "Output: #{error.message}"
156
343
  end
157
344
  ```
158
345
 
159
346
  ```text
160
- K9O_NOT_FOUND 404 req_01H...
161
- Collection not found
347
+ Output: K9O_NOT_FOUND 404 req_01H...
348
+ Output: Collection not found
162
349
  ```
163
350
 
164
351
  - See [error attributes](#error-attributes) for every field.
@@ -179,9 +366,9 @@ Collection not found
179
366
  | Option | What it does |
180
367
  |---|---|
181
368
  | `api_key:` | Bearer token sent with every request. |
182
- | `org_slug:` | Organization. On the default host the SDK uses `https://<org_slug>.api.k9o.io`. |
369
+ | `org_slug:` | Organization. Prefixed to a platform host (`api.<apex>`) to form the org host. |
183
370
  | `project_slug:` | Project. Required by every project-scoped namespace. |
184
- | `base_url:` | Scheme and host only. Defaults to `https://api.k9o.io`. |
371
+ | `base_url:` | Scheme and host only. Defaults to the public host `https://api.k9o.io`. |
185
372
  | `default_headers:` | Extra headers added to every request. |
186
373
  | `http_client:` | A `Net::HTTP`-compatible object used instead of the default connection. |
187
374
 
@@ -228,6 +415,8 @@ Chain filters before `execute`. Values are matched against one column.
228
415
  | `limit(n)` | Maximum rows to return. |
229
416
  | `cursor(token)` | Continue from `meta.cursor.next`. |
230
417
  | `count(mode)` | Row count mode: `none`, `exact` or `estimated`. |
418
+ | `text_search(column, query, language: "english", type: "plain")` | Full-text search over one column. |
419
+ | `vector_search(column, embedding, metric: "cosine", limit: nil)` | Nearest rows to an embedding, closest first. |
231
420
 
232
421
  #### Projects
233
422
 
@@ -236,9 +425,18 @@ Chain filters before `execute`. Values are matched against one column.
236
425
  | Method | What it does |
237
426
  |---|---|
238
427
  | `k9o.projects.list` | List projects you can access. |
239
- | `k9o.projects.create` | Create a project from `slug` and `name`. |
428
+ | `k9o.projects.create` | Create a project from `display_name:` (optional `slug:`, `region:`, `custody_mode:`, more). |
240
429
  | `k9o.projects.get` | Read a project by slug. |
241
- | `k9o.projects.update` | Change a project's `name`. |
430
+ | `k9o.projects.update` | Change a project by slug: `display_name:`, `slug:` (rename), `description:`, `region:`, more. |
431
+
432
+ ```ruby
433
+ project = k9o.projects.create(display_name: "Task app", slug: "task-app")
434
+ puts "Output: #{project.data["slug"]}"
435
+ ```
436
+
437
+ ```text
438
+ Output: task-app
439
+ ```
242
440
 
243
441
  #### Gate (end-user sign-in)
244
442
 
@@ -248,20 +446,18 @@ Chain filters before `execute`. Values are matched against one column.
248
446
  |---|---|
249
447
  | `k9o.gate.sign_up` | Create an end user from email and password. |
250
448
  | `k9o.gate.sign_in` | Sign in with email and password. |
251
- | `k9o.gate.sign_out` | Revoke the session's refresh token. |
449
+ | `k9o.gate.sign_out` | Revoke a session. Requires `refresh_token:`. |
252
450
  | `k9o.gate.whoami` | Return the signed-in identity. |
253
451
  | `k9o.gate.refresh_token` | Exchange a refresh token for a new access token. |
254
- | `k9o.gate.verify_session` | Check that the current session is valid. |
255
- | `k9o.gate.hosted` | Return the hosted sign-in `signInUrl` and `rpId`. |
256
452
 
257
453
  ```ruby
258
454
  session = k9o.gate.sign_in(email: "ada@example.com", password: "correct-horse")
259
455
  user = session.data["user"]
260
- puts user["email"]
456
+ puts "Output: #{user["email"]}"
261
457
  ```
262
458
 
263
459
  ```text
264
- ada@example.com
460
+ Output: ada@example.com
265
461
  ```
266
462
 
267
463
  #### Storage
@@ -303,11 +499,32 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
303
499
 
304
500
  | Method | What it does |
305
501
  |---|---|
306
- | `k9o.functions.deploy` | Deploy a function from `name`, `runtime` and `code`. |
307
- | `k9o.functions.list` | List functions. |
502
+ | `k9o.functions.upload_artifact` | Upload the build (`bytes`, `kind` of `"wasm"` or `"tar"`). Returns `artifactKey`. |
503
+ | `k9o.functions.deploy` | Deploy from `name:`, `runtime:`, `tier:`, `artifact_key:`, `version:`, optional `description:`. |
504
+ | `k9o.functions.list` | List functions. Options: `status:`, `limit:`, `cursor:`. |
308
505
  | `k9o.functions.get` | Read one function by name. |
309
506
  | `k9o.functions.delete` | Remove a function. |
310
- | `k9o.functions.invoke` | Run a function with a payload. |
507
+ | `k9o.functions.invoke` | Run a function. The `payload` argument is sent as `{payload}`. |
508
+
509
+ `tier:` is `"sandbox"` (an OCI image archive, `"tar"`, up to 256 MiB). The `gateway` tier is for functions owned by platform operators and is not available to most projects. Sandbox functions run in a [Firelock](https://github.com/firelock-sandbox/firelock) microVM sandbox.
510
+
511
+ ```ruby
512
+ artifact = k9o.functions.upload_artifact(File.binread("hello.tar"), "tar")
513
+
514
+ k9o.functions.deploy(
515
+ name: "hello",
516
+ runtime: "bun",
517
+ tier: "sandbox",
518
+ artifact_key: artifact.data["artifactKey"],
519
+ version: "1.0.0"
520
+ )
521
+ result = k9o.functions.invoke("hello", { "name" => "Ada" })
522
+ puts "Output: #{result.data["greeting"]}"
523
+ ```
524
+
525
+ ```text
526
+ Output: Hello, Ada
527
+ ```
311
528
 
312
529
  #### Migrations
313
530
 
@@ -331,11 +548,11 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
331
548
  | Method | What it does |
332
549
  |---|---|
333
550
  | `k9o.policies.current` | Read the published bundle. |
334
- | `k9o.policies.list` | List bundles. |
335
- | `k9o.policies.create` | Create a bundle from `name` and `rules`. |
336
- | `k9o.policies.validate` | Check rules without saving. |
551
+ | `k9o.policies.list` | List bundles. Only option: `status:`. |
552
+ | `k9o.policies.create` | Create a bundle from a `policy:` document (optional `rules:`, `editor_mode:`). |
553
+ | `k9o.policies.validate` | Check a `policy:` document (and optional `rules:`) without saving. |
337
554
  | `k9o.policies.get` | Read one bundle. |
338
- | `k9o.policies.update` | Change a bundle. |
555
+ | `k9o.policies.update` | Replace a bundle's `policy:` (optional `rules:`, `editor_mode:`). |
339
556
  | `k9o.policies.delete` | Remove a bundle. |
340
557
  | `k9o.policies.publish` | Make a bundle current. |
341
558
 
@@ -345,7 +562,9 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
345
562
 
346
563
  | Method | What it does |
347
564
  |---|---|
348
- | `k9o.audit.query` | Search events by time range, type and more. |
565
+ | `k9o.audit.query` | Filter by `event_type:`, `actor_id:`, `after:`, `before:`, `limit:`, `cursor:`. |
566
+
567
+ `after:` and `before:` are ISO 8601 timestamps.
349
568
 
350
569
  #### AI embeddings
351
570
 
@@ -354,6 +573,21 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
354
573
  | Method | What it does |
355
574
  |---|---|
356
575
  | `k9o.ai.embed(input:, model: nil)` | Embed one string or a list of strings. |
576
+ | `k9o.ai.generate(prompt, model: nil, max_tokens: nil, temperature: nil, usage_type: nil)` | Generate text from a prompt. |
577
+
578
+ ##### Generate text
579
+
580
+ `model` is optional: without it the org's default model is used. Models from outside providers answer 501 until built.
581
+ The result `data` is the same as `compute.llms.invoke`.
582
+
583
+ ```ruby
584
+ result = k9o.ai.generate("Say hi in three words", max_tokens: 20)
585
+ puts "Output: #{result.data['content']}"
586
+ ```
587
+
588
+ ```text
589
+ Output: Hi there, friend!
590
+ ```
357
591
 
358
592
  #### Language models
359
593
 
@@ -364,7 +598,18 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
364
598
  | `k9o.compute.llms.list` | List available models. |
365
599
  | `k9o.compute.llms.read` | Read one model. |
366
600
  | `k9o.compute.llms.invoke` | Send a prompt and get the reply. |
367
- | `k9o.compute.llms.invoke_stream` | Stream the reply token by token (returns an `Enumerator`). |
601
+ | `k9o.compute.llms.invoke_stream` | Stream the reply chunk by chunk (returns an `Enumerator`). |
602
+
603
+ Pass settings in `params:` (`max_tokens`, `temperature`, `usage_type`). The result `data` has `content` and `usage`.
604
+
605
+ ```ruby
606
+ reply = k9o.compute.llms.invoke("llama-3-8b", prompt: "Say hi", params: { max_tokens: 20 })
607
+ puts "Output: #{reply.data['content']} #{reply.data['usage']['totalTokens']}"
608
+ ```
609
+
610
+ ```text
611
+ Output: Hi there! 14
612
+ ```
368
613
 
369
614
  #### Realtime
370
615
 
@@ -372,12 +617,52 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
372
617
 
373
618
  | Method | What it does |
374
619
  |---|---|
375
- | `k9o.realtime.subscribe` | Stream live events (returns an `Enumerator`). |
376
- | `k9o.realtime.replay` | Read past events from a sequence number. |
377
- | `k9o.realtime.subscribe_poll` | Poll for events from a sequence number. |
620
+ | `k9o.realtime.subscribe` | Stream live events over SSE (returns an `Enumerator`). |
378
621
 
379
622
  `K9o::Realtime::Topic` builds topic names, for example `Topic.collection("tasks")`.
380
623
 
624
+ #### Streams
625
+
626
+ `k9o.streams` publishes to and subscribes to the project's user streams. It needs `project_slug`.
627
+
628
+ | Method | What it does |
629
+ |---|---|
630
+ | `k9o.streams.create(display_name, max_bytes, retention_seconds: nil)` | Create a stream. The slug comes from the name. |
631
+ | `k9o.streams.list` | List the project's streams. |
632
+ | `k9o.streams.get(slug)` | Read one stream. |
633
+ | `k9o.streams.delete(slug)` | Retire a stream. |
634
+ | `k9o.streams.publish(slug, data, subject: nil)` | Append an event. `subject` is a dot-separated routing suffix. |
635
+ | `k9o.streams.replay(slug, from_sequence: nil, until_sequence: nil, limit: nil)` | Read past events: `events`, `nextCursor`, `hasMore`. |
636
+ | `k9o.streams.subscribe(slug, from_sequence: nil)` | Tail live events over SSE (an `Enumerator`). Reconnects from the last sequence. |
637
+
638
+ #### Approvals
639
+
640
+ `k9o.approvals` reads and decides the queue of changes waiting for approval. It needs `project_slug`.
641
+
642
+ | Method | What it does |
643
+ |---|---|
644
+ | `k9o.approvals.list(status: nil, limit: nil)` | Newest pending changes first. Not paginated; `limit` is 1 to 200. |
645
+ | `k9o.approvals.get(id)` | Read one change. |
646
+ | `k9o.approvals.approve(id, reason: nil)` | Approve. `status` can stay `pending_approval` while more approvers are needed. |
647
+ | `k9o.approvals.reject(id, reason:)` | Reject. `reason` is required. |
648
+ | `k9o.approvals.bulk(action:, criteria:, reason: nil)` | Approve or reject every change matching a predicate. `reason` is required to reject. Failed rows are skipped. |
649
+
650
+ #### Approval policies
651
+
652
+ `k9o.approval_policies` holds the rules that decide which writes are queued. It needs `project_slug`.
653
+
654
+ | Method | What it does |
655
+ |---|---|
656
+ | `k9o.approval_policies.list` | All policies, oldest first. |
657
+ | `k9o.approval_policies.create(collection:, operation:, action:, predicate:)` | Create a policy. `action` is `auto_approve` or `queue`. |
658
+ | `k9o.approval_policies.delete(id)` | Delete a policy. |
659
+
660
+ #### Schema
661
+
662
+ | Method | What it does |
663
+ |---|---|
664
+ | `k9o.schema.collections.list(limit: nil, cursor: nil)` | List the project's collections by name, with `meta.cursor.next` paging. |
665
+
381
666
  #### Studio
382
667
 
383
668
  `k9o.studio` manages workflows and dashboards.
@@ -391,7 +676,7 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
391
676
  | `k9o.studio.workflows.read` | Read one workflow. |
392
677
  | `k9o.studio.workflows.update` | Change a workflow. |
393
678
  | `k9o.studio.workflows.delete` | Delete a workflow. |
394
- | `k9o.studio.workflows.trigger` | Start a run. |
679
+ | `k9o.studio.workflows.trigger` | Start a run, with an optional context hash. |
395
680
  | `k9o.studio.workflows.runs.list` | List a workflow's runs. |
396
681
  | `k9o.studio.workflows.runs.read` | Read one run. |
397
682
  | `k9o.studio.workflows.runs.cancel` | Cancel a run. |
@@ -415,19 +700,13 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
415
700
  | Method | What it does |
416
701
  |---|---|
417
702
  | `k9o.me.profile.get` | Read the profile. |
418
- | `k9o.me.profile.update` | Change the display name. |
703
+ | `k9o.me.profile.update` | Change `display_name:`, `locale:` or `timezone:` (only those given). |
419
704
  | `k9o.me.sessions.list` | List active sessions. |
420
705
  | `k9o.me.sessions.revoke` | End one session. |
421
706
  | `k9o.me.sessions.revoke_all` | End all sessions. |
422
707
  | `k9o.me.api_tokens.list` | List API tokens. |
423
- | `k9o.me.api_tokens.create` | Create an API token. |
708
+ | `k9o.me.api_tokens.create` | Create a token from `label:` and `scope_manifest:` (optional `ttl_seconds:`). |
424
709
  | `k9o.me.api_tokens.delete` | Delete an API token. |
425
- | `k9o.me.notifications.list` | List notifications. |
426
- | `k9o.me.notifications.mark_read` | Mark one as read. |
427
- | `k9o.me.notifications.mark_all_read` | Mark all as read. |
428
- | `k9o.me.audit_log.query` | Search your own audit events. |
429
- | `k9o.me.data_export.request` | Request a data export. |
430
- | `k9o.me.data_export.status` | Check an export job. |
431
710
 
432
711
  #### Organization
433
712
 
@@ -447,30 +726,26 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
447
726
  | Method | What it does |
448
727
  |---|---|
449
728
  | `k9o.org.get` | Read the active organization. |
450
- | `k9o.org.members.list` | List members. |
729
+ | `k9o.org.members.list` | List members. Options: `user_id:`, `role:`, `acceptance_status:`, `status:`, `state:`. |
451
730
  | `k9o.org.members.invite` | Invite a member by email and role. |
452
- | `k9o.org.members.get` | Read one member. |
453
731
  | `k9o.org.members.remove` | Remove a member. |
454
- | `k9o.org.groups.list` | List groups. |
455
- | `k9o.org.groups.create` | Create a group. |
732
+ | `k9o.org.groups.list` | List groups, optionally for one `user_id:`. |
456
733
  | `k9o.org.groups.get` | Read one group. |
457
- | `k9o.org.groups.delete` | Delete a group. |
458
- | `k9o.org.audit_log.query` | Search organization audit events. |
734
+ | `k9o.org.audit_log.query` | Search organization audit events by `type:` and `limit:`. |
459
735
  | `k9o.org.gate.roles.list` | List role grants. |
460
- | `k9o.org.gate.roles.grant` | Grant a role to a user. |
736
+ | `k9o.org.gate.roles.grant` | Grant a role to a user (`user_id:`, `role:`, optional `decision_reason:`). |
461
737
  | `k9o.org.gate.roles.get` | Read one grant. |
462
738
  | `k9o.org.gate.roles.revoke` | Revoke a grant. |
463
739
  | `k9o.org.gate.idp_providers.list` | List identity providers. |
464
- | `k9o.org.gate.idp_providers.create` | Add an identity provider. |
740
+ | `k9o.org.gate.idp_providers.create` | Add a provider: `idp_provider:`, `display_name:`, `client_id:`, `client_secret_ref:`, `tier_gating:`. |
465
741
  | `k9o.org.gate.idp_providers.get` | Read one provider. |
466
742
  | `k9o.org.gate.idp_providers.configure` | Change a provider. |
467
743
  | `k9o.org.gate.idp_providers.delete` | Remove a provider. |
468
- | `k9o.org.smtp.get` | Read the mail server settings. |
469
- | `k9o.org.smtp.set` | Save the mail server settings. |
470
- | `k9o.org.smtp.test` | Send a test email. |
744
+ | `k9o.org.smtp.set` | Save the mail server: `host:`, `port:`, `username:`, `password_ref:`, `from_address:`. |
745
+ | `k9o.org.smtp.test` | Send a test email to `to:`, with an optional `subject:`. |
471
746
  | `k9o.org.smtp.unset` | Remove the mail server settings. |
472
747
  | `k9o.org.default_llm.get` | Read the default model. |
473
- | `k9o.org.default_llm.set` | Set the default model. |
748
+ | `k9o.org.default_llm.set` | Set the default model from a model id. |
474
749
 
475
750
  #### Vault secrets
476
751
 
@@ -479,7 +754,7 @@ signed = avatars.create_signed_url("ada.png", expires_in: 300)
479
754
  | Method | What it does |
480
755
  |---|---|
481
756
  | `k9o.kibo.vault.secrets.list` | List secrets by prefix. |
482
- | `k9o.kibo.vault.secrets.get` | Read a secret's details. |
757
+ | `k9o.kibo.vault.secrets.get` | Read a secret by name (`region:` is required). |
483
758
  | `k9o.kibo.vault.secrets.create` | Store a new secret. |
484
759
  | `k9o.kibo.vault.secrets.reveal_challenge` | Get a one-time challenge for revealing. |
485
760
  | `k9o.kibo.vault.secrets.reveal` | Reveal the value using the challenge. |
@@ -534,10 +809,12 @@ The server may send codes not listed here, so keep a fallback branch.
534
809
 
535
810
  ### Regional hosts
536
811
 
537
- - Organization hosts such as `https://acme.api.k9o.io` are region-free.
538
- - The SDK sends one credential-free `HEAD` request to learn the region.
539
- - Later requests go to the regional host.
540
- - Any other base URL is used exactly as given.
812
+ - A platform host is `api.<apex>` or `api-staging.<apex>`, on any apex.
813
+ - With `org_slug`, the SDK builds the org host `<org_slug>.<host>`.
814
+ - A region-free org host gets one credential-free `HEAD` request to learn its region.
815
+ - Later requests go to the regional host, which must share the org, family and apex.
816
+ - Any other base URL (internal, air-gapped, localhost) is used exactly as given.
817
+ - `https://api.k9o.io` is only the default when `base_url:` is omitted.
541
818
 
542
819
  ### Related packages
543
820
 
@@ -546,6 +823,20 @@ The server may send codes not listed here, so keep a fallback branch.
546
823
  | `@k9o/core` | TypeScript SDK |
547
824
  | `@k9o/cli` | Command-line tool |
548
825
 
826
+ ### About Kilimanjaro
827
+
828
+ Kilimanjaro is a modular developer platform for building secure enterprise applications.
829
+ You define AI agents, functions and data pipelines on the platform.
830
+ The SDKs let you deploy and invoke them, read and write your data, publish to and subscribe to streams,
831
+ sign users in, and encrypt with keys you hold.
832
+
833
+ It provides four building blocks:
834
+
835
+ - **Keep**: AI security and governance. Guardrails and policy for the agents you run.
836
+ - **Pulse**: data intelligence and real-time stream processing. Streams, analytics and live feeds.
837
+ - **Gate**: customer identity and access management. Sign-in and sessions, and automatic data scoping so a signed-in user reaches only their own data.
838
+ - **Kibo**: cryptographic keys and secrets management. Client-side encryption with keys you hold, and a secrets vault.
839
+
549
840
  ## License
550
841
 
551
842
  Apache-2.0 © Formelo Inc. See `LICENSE` and `NOTICE`.