k9o 1.3.0 → 1.5.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -1
  3. data/README.md +1516 -159
  4. data/k9o.gemspec +2 -2
  5. data/lib/k9o/ai.rb +15 -0
  6. data/lib/k9o/analytics.rb +403 -0
  7. data/lib/k9o/approvals.rb +253 -0
  8. data/lib/k9o/audit.rb +150 -0
  9. data/lib/k9o/client.rb +25 -4
  10. data/lib/k9o/crypto.rb +471 -0
  11. data/lib/k9o/custody.rb +28 -0
  12. data/lib/k9o/data.rb +125 -5
  13. data/lib/k9o/ed25519.rb +182 -0
  14. data/lib/k9o/end_users.rb +93 -0
  15. data/lib/k9o/envelope.rb +20 -5
  16. data/lib/k9o/functions.rb +82 -3
  17. data/lib/k9o/gate.rb +46 -7
  18. data/lib/k9o/http_transport.rb +71 -14
  19. data/lib/k9o/jcs.rb +117 -0
  20. data/lib/k9o/kerror.rb +57 -5
  21. data/lib/k9o/keys.rb +131 -0
  22. data/lib/k9o/kibo_client_core.rb +385 -0
  23. data/lib/k9o/kibo_ffi_core.rb +178 -0
  24. data/lib/k9o/locate.rb +118 -0
  25. data/lib/k9o/me.rb +1 -1
  26. data/lib/k9o/metadata.rb +342 -0
  27. data/lib/k9o/moat.rb +443 -0
  28. data/lib/k9o/org.rb +1 -1
  29. data/lib/k9o/query.rb +174 -0
  30. data/lib/k9o/rampart.rb +273 -0
  31. data/lib/k9o/realtime.rb +261 -10
  32. data/lib/k9o/schedules.rb +109 -0
  33. data/lib/k9o/schema.rb +35 -0
  34. data/lib/k9o/streams.rb +94 -0
  35. data/lib/k9o/vault.rb +3 -4
  36. data/lib/k9o/vectors.rb +124 -0
  37. data/lib/k9o/version.rb +1 -1
  38. data/lib/k9o.rb +17 -0
  39. data/sig/k9o/ai.rbs +4 -0
  40. data/sig/k9o/approvals.rbs +71 -0
  41. data/sig/k9o/datasets.rbs +125 -0
  42. data/sig/k9o/gate.rbs +2 -0
  43. data/sig/k9o/kerror.rbs +24 -0
  44. data/sig/k9o/kibo_client_core.rbs +25 -0
  45. data/sig/k9o/metadata.rbs +54 -0
  46. data/sig/k9o/moat.rbs +29 -0
  47. data/sig/k9o/query.rbs +26 -0
  48. data/sig/k9o/rampart.rbs +59 -0
  49. data/sig/k9o/realtime.rbs +34 -0
  50. data/sig/k9o/workload.rbs +64 -0
  51. metadata +33 -4
data/lib/k9o/schema.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module K9o
4
+ # Schema: k9o.schema.*
5
+ #
6
+ # collections.list(limit:, cursor:) GET /@<slug>/schema/collections
7
+ #
8
+ # The list is cursor-paginated and ordered by collection name: pass +meta.cursor.next+ of one page as +cursor+ of
9
+ # the next, until it is nil.
10
+ class Schema
11
+ attr_reader :collections
12
+
13
+ def initialize(config)
14
+ @collections = SchemaCollections.new(config)
15
+ end
16
+ end
17
+
18
+ class SchemaCollections
19
+ def initialize(config)
20
+ @config = config
21
+ end
22
+
23
+ # The project's collections by name, one page at a time. +limit+ is 1 to 200 (default 50).
24
+ def list(limit: nil, cursor: nil)
25
+ route = UrlGrammar.project_path(Studio.require_project_slug(@config, 'schema'), 'schema/collections')
26
+ ApprovalChecks.check_limit(limit)
27
+ raise ApprovalChecks.invalid('cursor must be a string', cursor: cursor) unless cursor.nil? || cursor.is_a?(String)
28
+
29
+ query = {}
30
+ query['limit'] = limit unless limit.nil?
31
+ query['cursor'] = cursor unless cursor.nil?
32
+ HttpTransport.request(config: @config, method: 'GET', path: route, query: query)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module K9o
4
+ # User data streams namespace: k9o.streams.*.
5
+ #
6
+ # Routes under /@<slug>/data-streams/user. Publish needs project admin; subscribe and replay need admin or
7
+ # auditor. While the stream data plane is unavailable the gateway answers K9O_STREAM_UNAVAILABLE (503); a bad
8
+ # subject answers K9O_STREAM_INVALID. Both raise K9o::KError.
9
+ class Streams
10
+ @reconnect_delay = 0.25
11
+
12
+ class << self
13
+ # Seconds to wait before reconnecting after the server closes a subscribe connection.
14
+ attr_accessor :reconnect_delay
15
+ end
16
+
17
+ def initialize(config)
18
+ @config = config
19
+ end
20
+
21
+ # POST /@<slug>/data-streams/user — +max_bytes+ is the storage ceiling; the slug is derived from +display_name+.
22
+ def create(display_name, max_bytes, retention_seconds: nil)
23
+ body = { 'displayName' => display_name, 'maxBytes' => max_bytes }
24
+ body['retentionSeconds'] = retention_seconds unless retention_seconds.nil?
25
+ call('POST', path, body)
26
+ end
27
+
28
+ # GET /@<slug>/data-streams/user — the project's active user streams.
29
+ def list
30
+ call('GET', path)
31
+ end
32
+
33
+ # GET /@<slug>/data-streams/user/<slug>
34
+ def get(slug)
35
+ call('GET', path(slug))
36
+ end
37
+
38
+ # DELETE /@<slug>/data-streams/user/<slug> — retire the stream.
39
+ def delete(slug)
40
+ call('DELETE', path(slug))
41
+ end
42
+
43
+ # POST .../<slug>/publish — +data+ is any JSON value; +subject+ is an optional dot-separated routing suffix.
44
+ def publish(slug, data, subject: nil)
45
+ body = { 'data' => data }
46
+ body['subject'] = subject unless subject.nil?
47
+ call('POST', path(slug, 'publish'), body)
48
+ end
49
+
50
+ # POST .../<slug>/replay — a bounded range of past events: data['events'], data['nextCursor'], data['hasMore'].
51
+ def replay(slug, from_sequence: nil, until_sequence: nil, limit: nil)
52
+ body = {}
53
+ body['fromSequence'] = from_sequence unless from_sequence.nil?
54
+ body['untilSequence'] = until_sequence unless until_sequence.nil?
55
+ body['limit'] = limit unless limit.nil?
56
+ call('POST', path(slug, 'replay'), body)
57
+ end
58
+
59
+ # GET .../<slug>/subscribe — an Enumerator tailing live events { 'sequence', 'subject', 'data', 'publishedAt' }.
60
+ # The gateway bounds each connection and closes it; the enumerator reconnects from the last sequence it yielded.
61
+ # Stop by leaving the +each+ block (break) or by taking a finite number (+first(n)+).
62
+ def subscribe(slug, from_sequence: nil)
63
+ route = path(slug, 'subscribe')
64
+ Enumerator.new do |yielder|
65
+ cursor = from_sequence
66
+ loop do
67
+ query = cursor.nil? ? nil : { 'fromSequence' => cursor.to_s }
68
+ HttpTransport.stream_sse(config: @config, method: 'GET', path: route, query: query).each do |event|
69
+ next unless event.is_a?(Hash) && event.key?('sequence')
70
+
71
+ cursor = event['sequence']
72
+ yielder << event
73
+ end
74
+ delay = self.class.reconnect_delay
75
+ sleep(delay) if delay.positive?
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def call(method, route, body = nil)
83
+ HttpTransport.request(config: @config, method: method, path: route, body: body)
84
+ end
85
+
86
+ def path(stream_slug = nil, suffix = nil)
87
+ project = Studio.require_project_slug(@config, 'streams')
88
+ sub = +'data-streams/user'
89
+ sub << "/#{::URI.encode_www_form_component(stream_slug)}" if stream_slug
90
+ sub << "/#{suffix}" if suffix
91
+ UrlGrammar.project_path(project, sub)
92
+ end
93
+ end
94
+ end
data/lib/k9o/vault.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
3
  require 'uri'
5
4
 
6
5
  module K9o
@@ -81,7 +80,7 @@ module K9o
81
80
  # `metadata` (`Hash[String, String]`) are passed through verbatim.
82
81
  def create(name:, value:, region: nil, visibility: nil, metadata: nil)
83
82
  slug = require_project_slug
84
- body = { 'value' => Base64.strict_encode64(value) }
83
+ body = { 'value' => [value].pack('m0') }
85
84
  body['visibility'] = visibility if visibility
86
85
  body['metadata'] = metadata if metadata
87
86
  HttpTransport.request(
@@ -119,7 +118,7 @@ module K9o
119
118
  )
120
119
  data = envelope.data
121
120
  if data.is_a?(Hash) && data.key?('value')
122
- data = data.merge('value' => Base64.decode64(data['value']))
121
+ data = data.merge('value' => data['value'].unpack1('m'))
123
122
  end
124
123
  Envelope.new(data: data, meta: envelope.meta)
125
124
  end
@@ -132,7 +131,7 @@ module K9o
132
131
  # schema claims — call `get` afterward if the full summary is needed.
133
132
  def update(name, value:, region: nil, metadata: nil)
134
133
  slug = require_project_slug
135
- body = { 'value' => Base64.strict_encode64(value) }
134
+ body = { 'value' => [value].pack('m0') }
136
135
  body['metadata'] = metadata if metadata
137
136
  HttpTransport.request(
138
137
  config: @config, method: 'PUT',
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module K9o
4
+ # Vector collections: k9o.vectors.* store embeddings you computed yourself, next to the text and metadata they came
5
+ # from, and find the nearest ones.
6
+ #
7
+ # nearest(collection, query_vector, top_k:, min_score:, metric:, filters:) POST /@<slug>/vectors/<collection>/search
8
+ # upsert(collection, rows) POST /@<slug>/vectors/<collection>/upsert
9
+ #
10
+ # Both send the vector in the request body, so they take real embeddings of any common size (up to 16000 numbers).
11
+ # The collection name must start with +_+. The first upsert creates the collection and fixes its dimension; every
12
+ # later vector must have the same length. Searching a collection that was never upserted answers 404
13
+ # K9O_COLLECTION_NOT_FOUND.
14
+ class Vectors
15
+ METRICS = %w[cosine euclidean dot].freeze
16
+ MAX_VECTOR = 16_000
17
+ MAX_ROWS = 1000
18
+ # Snake_case spellings accepted for the camelCase wire keys of a row and of its +_source+.
19
+ ROW_ALIASES = { 'content_hash' => 'contentHash', 'subject_ids' => 'subjectIds' }.freeze
20
+ SOURCE_ALIASES = { 'chunk_index' => 'chunkIndex' }.freeze
21
+
22
+ def initialize(config)
23
+ @config = config
24
+ end
25
+
26
+ # The rows nearest to +query_vector+ (1 to 16000 numbers), closest first. +top_k+ is 1 to 1000 (the gateway
27
+ # defaults to 10); +min_score+ drops results scoring below it; +metric+ is cosine (default), euclidean or dot;
28
+ # +filters+ is a Hash of equality checks on +id+, +contentHash+, +source.<key>+ and +metadata.<key>+.
29
+ # +data+ is { 'results' => [...], 'collection' => name }.
30
+ def nearest(collection, query_vector, top_k: nil, min_score: nil, metric: nil, filters: nil)
31
+ route = path(collection, 'search')
32
+ check_vector(query_vector, 'query_vector')
33
+ body = { 'queryVector' => query_vector.to_a }
34
+ unless top_k.nil?
35
+ unless top_k.is_a?(Integer) && top_k >= 1 && top_k <= 1000
36
+ raise invalid('top_k must be a whole number from 1 to 1000', top_k: top_k)
37
+ end
38
+
39
+ body['topK'] = top_k
40
+ end
41
+ unless min_score.nil?
42
+ raise invalid('min_score must be a number', min_score: min_score) unless finite?(min_score)
43
+
44
+ body['minScore'] = min_score
45
+ end
46
+ body['metric'] = ApprovalChecks.enum!(metric, METRICS, 'metric') unless metric.nil?
47
+ unless filters.nil?
48
+ raise invalid('filters must be a Hash of path to value') unless filters.is_a?(Hash)
49
+
50
+ body['filters'] = filters.transform_keys(&:to_s)
51
+ end
52
+ call(route, body)
53
+ end
54
+
55
+ # Insert or overwrite 1 to 1000 rows in one request; the first upsert creates the collection. A row is a Hash
56
+ # with +vector+ and +_source+ ({ handle:, chunkIndex: }), and optionally +id+, +contentHash+, +text+, +metadata+
57
+ # and +subjectIds+ (one end-user id, ["eu_..."]; required unless the collection was declared shared). Keys may
58
+ # be strings or symbols, and +content_hash+, +subject_ids+ and +chunk_index+ are accepted for the camelCase
59
+ # names. A single row may be passed on its own, with or without braces. +data+ is { 'upserted' => n,
60
+ # 'collection' => name }.
61
+ def upsert(collection, rows = nil, **row)
62
+ route = path(collection, 'upsert')
63
+ rows = [row] if rows.nil? && !row.empty?
64
+ rows = [rows] if rows.is_a?(Hash)
65
+ unless rows.is_a?(Array) && rows.size >= 1 && rows.size <= MAX_ROWS
66
+ raise invalid("rows must be an array of 1 to #{MAX_ROWS} rows")
67
+ end
68
+
69
+ wire = rows.each_with_index.map do |entry, index|
70
+ raise invalid("rows[#{index}] must be a Hash") unless entry.is_a?(Hash)
71
+
72
+ normalize_row(entry, index)
73
+ end
74
+ call(route, 'rows' => wire)
75
+ end
76
+
77
+ private
78
+
79
+ def normalize_row(entry, index)
80
+ row = entry.each_with_object({}) do |(key, value), out|
81
+ name = key.to_s
82
+ out[ROW_ALIASES.fetch(name, name)] = value
83
+ end
84
+ check_vector(row['vector'], "rows[#{index}].vector")
85
+ source = row['_source']
86
+ if source.is_a?(Hash)
87
+ row['_source'] = source.each_with_object({}) do |(key, value), out|
88
+ name = key.to_s
89
+ out[SOURCE_ALIASES.fetch(name, name)] = value
90
+ end
91
+ end
92
+ row['vector'] = row['vector'].to_a
93
+ row
94
+ end
95
+
96
+ def check_vector(vector, name)
97
+ unless vector.is_a?(Array) && vector.size >= 1 && vector.size <= MAX_VECTOR
98
+ raise invalid("#{name} must be an array of 1 to #{MAX_VECTOR} numbers")
99
+ end
100
+ return if vector.all? { |n| finite?(n) }
101
+
102
+ raise invalid("#{name} must contain only finite numbers")
103
+ end
104
+
105
+ def finite?(value)
106
+ value.is_a?(Numeric) && !value.is_a?(Complex) && (!value.respond_to?(:finite?) || value.finite?)
107
+ end
108
+
109
+ def invalid(message, context = {})
110
+ ApprovalChecks.invalid(message, context)
111
+ end
112
+
113
+ def call(route, body)
114
+ HttpTransport.request(config: @config, method: 'POST', path: route, body: body)
115
+ end
116
+
117
+ def path(collection, tail)
118
+ raise invalid('a vector collection name must start with _', collection: collection) unless collection.is_a?(String) && collection.start_with?('_')
119
+
120
+ slug = Studio.require_project_slug(@config, 'vectors')
121
+ UrlGrammar.project_path(slug, "vectors/#{::URI.encode_www_form_component(collection).gsub('+', '%20')}/#{tail}")
122
+ end
123
+ end
124
+ end
data/lib/k9o/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module K9o
4
- VERSION = '1.3.0'
4
+ VERSION = '1.5.0'
5
5
  end
data/lib/k9o.rb CHANGED
@@ -29,15 +29,30 @@ require_relative 'k9o/url_grammar'
29
29
  require_relative 'k9o/region'
30
30
  require_relative 'k9o/http_transport'
31
31
  require_relative 'k9o/data'
32
+ require_relative 'k9o/query'
32
33
  require_relative 'k9o/projects'
34
+ require_relative 'k9o/locate'
33
35
  require_relative 'k9o/gate'
34
36
  require_relative 'k9o/studio'
35
37
  require_relative 'k9o/compute'
36
38
  require_relative 'k9o/ai'
37
39
  require_relative 'k9o/storage'
38
40
  require_relative 'k9o/realtime'
41
+ require_relative 'k9o/streams'
42
+ require_relative 'k9o/approvals'
43
+ require_relative 'k9o/schema'
39
44
  require_relative 'k9o/migrations'
40
45
  require_relative 'k9o/functions'
46
+ require_relative 'k9o/schedules'
47
+ require_relative 'k9o/vectors'
48
+ require_relative 'k9o/metadata'
49
+ require_relative 'k9o/rampart'
50
+ require_relative 'k9o/moat'
51
+ require_relative 'k9o/custody'
52
+ require_relative 'k9o/analytics'
53
+ require_relative 'k9o/keys'
54
+ require_relative 'k9o/crypto'
55
+ require_relative 'k9o/end_users'
41
56
  require_relative 'k9o/policies'
42
57
  require_relative 'k9o/audit'
43
58
  require_relative 'k9o/orgs'
@@ -46,6 +61,8 @@ require_relative 'k9o/org'
46
61
  # kibo ZK BYOK/HYOK crypto — self-guards its native binding (LoadError-safe), so
47
62
  # requiring k9o never breaks on a platform without the kibo native lib.
48
63
  require_relative 'k9o/kibo'
64
+ require_relative 'k9o/kibo_client_core'
65
+ require_relative 'k9o/kibo_ffi_core'
49
66
  # kibo vault REST surface (client.kibo.vault.secrets) —.
50
67
  require_relative 'k9o/vault'
51
68
  # Barbican guardrail SDK — self-guards its native binding (LoadError-safe), so
data/sig/k9o/ai.rbs CHANGED
@@ -8,6 +8,10 @@ module K9o
8
8
  # { "embeddings" => Array[Array[Float]], "model" => String, "dimensions" => Integer }.
9
9
  def initialize: (untyped config) -> void
10
10
  def embed: (input: (String | Array[String]), ?model: String?) -> Envelope
11
+
12
+ # Only the fields given are sent. Returns an Envelope whose `data` is
13
+ # { "modelId" => String, "content" => String, "finishReason" => String, "usage" => Hash, "usageType" => String }.
14
+ def generate: (String prompt, ?model: String?, ?max_tokens: Integer?, ?temperature: Numeric?, ?usage_type: String?) -> Envelope
11
15
  end
12
16
 
13
17
  class K9oClient
@@ -0,0 +1,71 @@
1
+ # RBS signatures for `client.approvals`, `client.approval_policies` and `client.schema`.
2
+
3
+ module K9o
4
+ module ApprovalChecks
5
+ def self.invalid: (String message, ?Hash[Symbol, untyped] context) -> KError
6
+ def self.check_limit: (untyped limit) -> void
7
+ def self.id_segment: (untyped value, String what) -> String
8
+ def self.reason!: (untyped reason) -> String
9
+ def self.predicate!: (untyped value, String field) -> Hash[untyped, untyped]
10
+ def self.enum!: (untyped value, Array[String] allowed, String field) -> String
11
+ end
12
+
13
+ class Approvals
14
+ attr_reader comments: ApprovalComments
15
+ def initialize: (untyped config) -> void
16
+ # One page, newest first. `limit` is 1 to 200 (default 50); pass `meta.cursor.next` back as `cursor`
17
+ # (nil on the last page).
18
+ def list: (?status: (String | Symbol)?, ?limit: Integer?, ?cursor: String?) -> Envelope
19
+ def get: (String id) -> Envelope
20
+ # `status` on the answer stays "pending_approval" while more approvers are needed.
21
+ def approve: (String id, ?reason: String?) -> Envelope
22
+ def reject: (String id, reason: String) -> Envelope
23
+ # HTTP 200 even when rows fail: `data["failed"]` is an Array of { "id", "code", "reason" } (empty when none).
24
+ # Compare `affectedIds` with what you intended.
25
+ def bulk: (action: (String | Symbol), criteria: Hash[(String | Symbol), untyped], ?reason: String?) -> Envelope
26
+ end
27
+
28
+ # `approvals.comments`. Posting needs a session (a personal access token alone is refused, K9O_RBAC_DENY).
29
+ class ApprovalComments
30
+ VISIBILITIES: Array[String]
31
+ BODY_MAX: Integer
32
+
33
+ # An Envelope whose data is the Array of comments, oldest first; `next_cursor` is nil on the last page.
34
+ # A comment is { "id", "caseId", "authorId", "visibility", "body", "createdAt" }.
35
+ class Page < Envelope
36
+ attr_reader next_cursor: String?
37
+ def initialize: (data: untyped, meta: Meta, ?next_cursor: String?) -> void
38
+ end
39
+
40
+ def initialize: (untyped config) -> void
41
+ # `body` is 1 to 4000 characters; `visibility` is "everyone" or "approvers_only", fixed once posted. HTTP 201.
42
+ def add: (String pending_id, body: String, visibility: (String | Symbol)) -> Envelope
43
+ # `limit` is 1 to 200 (default 50); pass `next_cursor` back as `cursor`.
44
+ def list: (String pending_id, ?limit: Integer?, ?cursor: String?) -> Page
45
+ end
46
+
47
+ class ApprovalPolicies
48
+ COLLECTION_RE: Regexp
49
+
50
+ def initialize: (untyped config) -> void
51
+ def list: () -> Envelope
52
+ def create: (collection: String, operation: (String | Symbol), action: (String | Symbol), predicate: Hash[(String | Symbol), untyped]) -> Envelope
53
+ def delete: (String id) -> Envelope
54
+ end
55
+
56
+ class SchemaCollections
57
+ def initialize: (untyped config) -> void
58
+ def list: (?limit: Integer?, ?cursor: String?) -> Envelope
59
+ end
60
+
61
+ class Schema
62
+ attr_reader collections: SchemaCollections
63
+ def initialize: (untyped config) -> void
64
+ end
65
+
66
+ class K9oClient
67
+ attr_reader approvals: Approvals
68
+ attr_reader approval_policies: ApprovalPolicies
69
+ attr_reader schema: Schema
70
+ end
71
+ end
@@ -0,0 +1,125 @@
1
+ # RBS signatures for `client.analytics` (datasets), `client.rollups` and `client.keys`.
2
+
3
+ module K9o
4
+ class Download
5
+ attr_reader body: String
6
+ attr_reader content_type: String?
7
+ def initialize: (body: String, ?content_type: String?) -> void
8
+ end
9
+
10
+ class AnalyticsRollups
11
+ def initialize: (untyped config) -> void
12
+ def create: (String dataset_slug, slug: String, source: String, measures: Array[Hash[Symbol | String, untyped]], window_seconds: Integer,
13
+ ?dimensions: Array[String]?, ?subject_field: String?, ?non_personal: bool?, ?reattest_interval_days: Integer?) -> Envelope
14
+ def list: (String dataset_slug) -> Envelope
15
+ def delete: (String dataset_slug, String rollup_id) -> Envelope
16
+ # People only: a service, workload or extension token gets 403 K9O_RBAC_DENY (reason human_only).
17
+ def reattest: (String dataset_slug, String rollup_id, ?reattest_interval_days: Integer?) -> Envelope
18
+ end
19
+
20
+ class AnalyticsSchedules
21
+ def initialize: (untyped config) -> void
22
+ def create: (String dataset_slug, from_collection: String, interval_seconds: Integer, ?reattest_interval_days: Integer?) -> Envelope
23
+ def list: (String dataset_slug) -> Envelope
24
+ def delete: (String dataset_slug, String schedule_id) -> Envelope
25
+ def reattest: (String dataset_slug, String schedule_id, ?reattest_interval_days: Integer?) -> Envelope
26
+ end
27
+
28
+ class Analytics
29
+ attr_reader rollups: AnalyticsRollups
30
+ attr_reader schedules: AnalyticsSchedules
31
+ def initialize: (untyped config) -> void
32
+ def list: () -> Envelope
33
+ def query: (String sql, Array[String] datasets) -> Envelope
34
+ def create: (String slug, display_name: String, columns: Array[Hash[Symbol | String, untyped]], residency_class: (String | Symbol),
35
+ ?partition_by: Array[String]?, ?storage_target: Hash[Symbol | String, untyped]?) -> Envelope
36
+ def get: (String slug) -> Envelope
37
+ def delete: (String slug) -> Envelope
38
+ def materialize: (String slug, ?from_collection: String?, ?from_query: String?) -> Envelope
39
+ def read: (String slug, ?partition_filter: Hash[Symbol | String, untyped]?, ?cursor: String?, ?limit: Integer?) -> Envelope
40
+ def write: (String slug, (Array[Hash[Symbol | String, untyped]] | String) rows, ?on_error: (String | Symbol)?) -> Envelope
41
+ def upload: (String slug, (String | IO) data, format: (String | Symbol), ?filename: String, ?manifest: Hash[Symbol | String, untyped]?,
42
+ ?dry_run: bool?, ?on_error: (String | Symbol)?, ?delimiter: String?, ?encoding: String?, ?has_header: bool?) -> Envelope
43
+ def download_original: (String slug) -> Download
44
+ end
45
+
46
+ class Rollups
47
+ def initialize: (untyped config) -> void
48
+ def list: () -> Envelope
49
+ def read: (String rollup_slug, ?from: (String | Time)?, ?to: (String | Time)?, ?dims: (Array[String] | String)?) -> Envelope
50
+ def subscribe: (String rollup_slug, ?from_sequence: Integer?, ?on_resync: (^(Hash[String, untyped]) -> untyped)?) -> Enumerator[Hash[String, untyped], untyped]
51
+ end
52
+
53
+ class Keys
54
+ STATUSES: Array[String]
55
+ def initialize: (untyped config) -> void
56
+ # data: { "items" => [key] } (not paged)
57
+ def list: (?status: (String | Symbol)?) -> Envelope
58
+ def get: (String key_id) -> Envelope
59
+ # data: { "keyId", "previousVersion", "newVersion", "status" => "rotated" }
60
+ # step_up: ^(StepUpChallenge, String, String) -> { method: String, proof: String }
61
+ def rotate: (String key_id, ?step_up: untyped, ?headers: Hash[String, String]?) -> Envelope
62
+
63
+ class StepUpChallenge < Struct[untyped]
64
+ attr_reader scope_set_root: String
65
+ attr_reader nonce: String
66
+ attr_reader min_tier: String?
67
+ attr_reader methods: Array[String]
68
+ attr_reader max_age_sec: Integer?
69
+ attr_reader single_use: bool?
70
+ attr_reader rp_id: String?
71
+ end
72
+ end
73
+
74
+ class Crypto
75
+ def initialize: (untyped config) -> void
76
+ def encrypt: (String crypt, Array[Hash[Symbol | String, untyped] | String] items) -> Envelope
77
+ def decrypt: (String crypt, Array[Hash[Symbol | String, untyped] | String] items) -> Envelope
78
+ def rewrap: (String crypt, Array[Hash[Symbol | String, untyped] | String] items) -> Envelope
79
+ def self.text: (Hash[String, untyped] item) -> String?
80
+ # Reads the project's custody mode; every encrypt does this first, with no cache.
81
+ def mode: () -> Mode
82
+ # Registers the Kibo client core (an object with encrypt, decrypt and rewrap); nil removes it.
83
+ def use_client_core: (untyped core) -> untyped
84
+ def self.check_expected_key_refs: (Array[String]? refs) -> Array[String]?
85
+ def self.key_ref_matches?: (String key_ref, Array[String] expected, provider: (:aws | :gcp | :azure | :vault)) -> bool
86
+
87
+ # Ask for a short-lived federated token for +op+ ("encrypt", "decrypt" or "rewrap") on +crypt+.
88
+ def federated_token: (String crypt, String op) -> FederatedToken
89
+
90
+ # Crypts: data is { "crypts" => [...] } (list) or one { "name", "status", "createdAt", "retiredAt" } Hash.
91
+ def crypts_list: () -> Envelope
92
+ def crypts_create: (String name) -> Envelope
93
+ def crypts_retire: (String name) -> Envelope
94
+
95
+ class Mode < Struct[untyped]
96
+ def custody_mode: () -> String?
97
+ def epoch: () -> String?
98
+ end
99
+
100
+ # +provider+ and +project_id+ are nil unless the gateway names them; they are only checked against configuration.
101
+ class FederatedToken < Struct[untyped]
102
+ attr_reader token: String
103
+ attr_reader expires_at: String
104
+ attr_reader issuer: String
105
+ attr_reader subject: String
106
+ attr_reader audience: String
107
+ attr_reader kid: String
108
+ attr_reader provider: String?
109
+ attr_reader project_id: String?
110
+ end
111
+ end
112
+
113
+ class Audit
114
+ # Yields { "auditEventId", "type", "actorType", "occurredAt", "outcome", "eventId" }.
115
+ def tail: (?types: (Array[String | Symbol] | String | Symbol)?, ?on_resync: (^(Hash[String, untyped]) -> untyped)?) -> Enumerator[Hash[String, untyped], untyped]
116
+ def replay: (?topic: String, ?after_audit_event_id: String?, ?from_sequence: Integer?, ?until_sequence: Integer?, ?limit: Integer?) -> Envelope
117
+ end
118
+
119
+ class K9oClient
120
+ attr_reader analytics: Analytics
121
+ attr_reader rollups: Rollups
122
+ attr_reader keys: Keys
123
+ attr_reader crypto: Crypto
124
+ end
125
+ end
data/sig/k9o/gate.rbs CHANGED
@@ -7,6 +7,8 @@ module K9o
7
7
  # The refresh token is required: the gateway answers 400 without it.
8
8
  def sign_out: (refresh_token: String) -> Envelope
9
9
  def whoami: () -> Envelope
10
+ def revoke_all_sessions: () -> Envelope
11
+ # `data` is `{ accessToken, refreshToken, expiresAt }`; the refresh token sent stops working, so store the new one.
10
12
  def refresh_token: (refresh_token: String) -> Envelope
11
13
  end
12
14
  end
@@ -0,0 +1,24 @@
1
+ # RBS signatures for the error type raised by every call.
2
+
3
+ module K9o
4
+ class KError < StandardError
5
+ attr_reader code: String
6
+ attr_reader context: Hash[untyped, untyped]
7
+ # Suggested next step as text, or nil.
8
+ attr_reader recommendation: String?
9
+ attr_reader status: Integer?
10
+ attr_reader request_id: String?
11
+ # Seconds the gateway asks the caller to wait before retrying, or nil.
12
+ attr_reader retry_after_sec: Numeric?
13
+ # The step-up challenge of a 401 K9O_STEP_UP_REQUIRED, or nil.
14
+ def step_up: () -> Keys::StepUpChallenge?
15
+
16
+ def initialize: (code: String, message: String, ?context: Hash[untyped, untyped], ?recommendation: String?, ?status: Integer?, ?request_id: String?, ?retry_after_sec: Numeric?) -> void
17
+ def with_recommendation: (String? rec) -> KError
18
+ def message: () -> String
19
+ def to_s: () -> String
20
+ def self.from_wire: (Hash[String, untyped] wire, Integer? status) -> KError
21
+ def self.recommendation_text: (untyped raw) -> String?
22
+ def self.retry_after: (untyped raw) -> Numeric?
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # RBS signatures for the Kibo client core of client.crypto (BYOK and HYOK projects).
2
+
3
+ module K9o
4
+ class KiboClientCore
5
+ EXCHANGE_TIMEOUT: String
6
+ CORE_FUNCTIONS: Array[Symbol]
7
+ PROVIDER_WIRE: Hash[Symbol, String]
8
+ SKEW_SECS: Integer
9
+ DEFAULT_DEADLINE_SECS: Integer
10
+
11
+ # +core+ responds to app_crypto_seal_blocking, app_crypto_open_blocking, acquire_kms_credential_blocking and
12
+ # core_capabilities (the generated KiboKmsRestFfi module does). +federated_token+ answers call(crypt, op) with a
13
+ # Crypto::FederatedToken.
14
+ def initialize: (provider: (:aws | :gcp | :azure | :vault), posture: (:byok | :hyok), key_ref: Hash[Symbol, untyped],
15
+ federation: Hash[Symbol | String, untyped], expected_key_refs: Array[String], federated_token: untyped, core: untyped,
16
+ ?project_id: String?, ?provider_config: Hash[Symbol | String, String?], ?exchange_deadline: Numeric,
17
+ ?now: ^() -> Integer) -> void
18
+ def enforces_expected_key_refs?: () -> true
19
+ def encrypt: (String crypt, Array[Hash[Symbol, untyped]] items, **untyped options) -> Hash[String, Array[Hash[String, untyped]]]
20
+ def decrypt: (String crypt, Array[Hash[Symbol, untyped]] items, **untyped options) -> Hash[String, Array[Hash[String, untyped]]]
21
+ def rewrap: (String crypt, Array[Hash[Symbol, untyped]] items, **untyped options) -> Hash[String, Array[Hash[String, untyped]]]
22
+ def inspect: () -> String
23
+ def to_s: () -> String
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ # RBS signatures for `client.metadata`.
2
+
3
+ module K9o
4
+ class Metadata
5
+ # An Envelope whose data is the Array of rows; `cursor` is the next page's cursor, or nil on the last page.
6
+ # A row is { "surface", "scope", "recordId" (collection) or "eventId" (telemetry), "metadata" => { column => value } }.
7
+ class Page < Envelope
8
+ def cursor: () -> String?
9
+ end
10
+
11
+ OPS: Array[String]
12
+ METRICS: Array[String]
13
+ SLICES: Array[String]
14
+ PREFIX: String
15
+ MAX_COLUMNS: Integer
16
+ MAX_LIMIT: Integer
17
+ TELEMETRY_FIELDS: Array[String]
18
+
19
+ attr_reader vectors: MetadataVectors
20
+ attr_reader index: MetadataIndex
21
+
22
+ def initialize: (untyped config) -> void
23
+ # surfaces: ["collection:posts"]; where: [{ field:, op:, value: }]; limit 1..1000; cursor only with one surface.
24
+ # Options may be keyword arguments or one Hash.
25
+ def filter: (?Hash[String | Symbol, untyped]? input, ?surfaces: Array[String], ?where: Array[Hash[String | Symbol, untyped]]?, ?limit: Integer?, ?cursor: String?) -> Page
26
+ # data is the whole updated record. Columns may be keyword arguments or one Hash.
27
+ def set: (String | Symbol collection, String id, ?Hash[String | Symbol, untyped]? columns, **untyped extra) -> Envelope
28
+ def search: (?Hash[String | Symbol, untyped]? input, ?surface: String, ?field: String | Symbol, ?q: String, ?limit: Integer?) -> Page
29
+ # Yields { "topic", "eventType", "payload" => { "collection", "id" }, "occurredAt", "eventId"? }.
30
+ def subscribe: (String surface, ?Hash[String | Symbol, untyped]? input, ?from_sequence: Integer?, ?fromSequence: Integer?, ?on_resync: (^(Hash[String, untyped]) -> untyped)?) -> Enumerator[Hash[String, untyped], untyped]
31
+ end
32
+
33
+ class MetadataVectors
34
+ def initialize: (untyped config) -> void
35
+ # Rows carry no score; at most 50 rows come back whatever k is.
36
+ def nearest: (?Hash[String | Symbol, untyped]? input, ?surface: String, ?column: String | Symbol, ?embedding: Array[Numeric], ?k: Integer?, ?metric: (String | Symbol)?, ?where: Array[Hash[String | Symbol, untyped]]?) -> Metadata::Page
37
+ end
38
+
39
+ class MetadataIndex
40
+ def initialize: (untyped config) -> void
41
+ # data is { "collection" => String, "columns" => [{ "column" => String, "indexed" => bool }] }.
42
+ def get: (String | Symbol collection) -> Envelope
43
+ def declare: (String | Symbol collection, Array[Hash[String | Symbol, untyped]] columns) -> Envelope
44
+ end
45
+
46
+ class Realtime
47
+ # The reconnecting reader behind subscribe; initial_query applies to the first connection only.
48
+ def feed: (String route, Hash[String, untyped] q, Hash[String, untyped]? initial_query, (^(Hash[String, untyped]) -> untyped)? on_resync) -> Enumerator[Hash[String, untyped], untyped]
49
+ end
50
+
51
+ class K9oClient
52
+ attr_reader metadata: Metadata
53
+ end
54
+ end