calmlens 0.21.0 → 0.23.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/README.md +2 -1
- data/lib/calmlens/assets.rb +48 -17
- data/lib/calmlens/cdn.rb +2 -2
- data/lib/calmlens/error_codes.rb +20 -0
- data/lib/calmlens/errors.rb +9 -0
- data/lib/calmlens/lenses.rb +1 -1
- data/lib/calmlens/option_keys.rb +10 -1
- data/lib/calmlens/project_configuration.rb +113 -0
- data/lib/calmlens/server_client.rb +10 -1
- data/lib/calmlens/session.rb +2 -3
- data/lib/calmlens/testing/classification.rb +1 -1
- data/lib/calmlens/testing/fake_api.rb +350 -45
- data/lib/calmlens/testing/mock_decision.rb +47 -6
- data/lib/calmlens/testing/test_inputs.rb +5 -5
- data/lib/calmlens/testing.rb +30 -9
- data/lib/calmlens/transform_schema.rb +0 -14
- data/lib/calmlens/transport.rb +3 -0
- data/lib/calmlens/upload_body.rb +85 -2
- data/lib/calmlens/version.rb +1 -1
- data/lib/calmlens.rb +1 -0
- data/sig/calmlens.rbs +29 -1
- data/sig/error_codes.rbs +10 -0
- data/sig/qualifiers.rbs +1 -0
- data/sig/types.rbs +173 -23
- metadata +3 -2
|
@@ -8,8 +8,7 @@ require_relative 'test_image_grid'
|
|
|
8
8
|
module CalmLens
|
|
9
9
|
# Signed test inputs: text codes, test images and media phrases that a project's moderation
|
|
10
10
|
# recognises as a declared scenario when they are signed with its secret test input key (shown in
|
|
11
|
-
# the portal's project settings to those who can read them)
|
|
12
|
-
# inputs" turned on, so CalmLens returns that scenario instead of classifying them. The formats
|
|
11
|
+
# the portal's project settings to those who can read them), so CalmLens returns that scenario instead of classifying them. The formats
|
|
13
12
|
# match the JavaScript SDK's `calmlens/test-inputs` byte for byte.
|
|
14
13
|
#
|
|
15
14
|
# A scenario is a Hash with the API's keys, as Strings or Symbols (snake_case keys are converted
|
|
@@ -57,7 +56,7 @@ module CalmLens
|
|
|
57
56
|
person = { 'box' => [2500, 1500, 5000, 8500] }
|
|
58
57
|
face = ->(age = nil) { { 'box' => [3500, 2000, 3000, 3500], **(age ? { 'age' => age } : {}) } }
|
|
59
58
|
# Ready-made scenarios (deeply frozen): a clean input, a limited one, adult images and text,
|
|
60
|
-
# regional delivery,
|
|
59
|
+
# regional delivery, delivery blocked in Germany, mixed instances in one category, and test
|
|
61
60
|
# images with one person and face, nobody, an adult's face, a minor's face and a group of three.
|
|
62
61
|
PRESETS = deep_freeze.call(
|
|
63
62
|
clean: { 'instances' => [] },
|
|
@@ -67,7 +66,7 @@ module CalmLens
|
|
|
67
66
|
regional_delivery: { 'instances' => [], 'regionalActions' => [{ 'region' => 'DE', 'action' => 'block' },
|
|
68
67
|
{ 'region' => 'GB', 'action' => 'age-restrict' },
|
|
69
68
|
{ 'region' => 'FR', 'action' => 'limit' }] },
|
|
70
|
-
germany:
|
|
69
|
+
germany: { 'instances' => [], 'regionalActions' => [{ 'region' => 'DE', 'action' => 'block' }] },
|
|
71
70
|
mixed: { 'instances' => [{ 'category' => 'harassment', 'qualifiers' => %w[harassment-abuse harassment-documentary] },
|
|
72
71
|
{ 'category' => 'harassment', 'qualifiers' => ['harassment-threat'] }] },
|
|
73
72
|
one_face: { 'instances' => [], 'detections' => { 'people' => [person], 'faces' => [face.call] } },
|
|
@@ -98,7 +97,8 @@ module CalmLens
|
|
|
98
97
|
preset = case signal
|
|
99
98
|
when 'harassment.insult' then PRESETS[:limited]
|
|
100
99
|
when 'sexual.adult' then PRESETS[kind == 'image' ? :adult_image : :adult_text]
|
|
101
|
-
when 'hate.regional-symbol'
|
|
100
|
+
when 'hate.regional-symbol'
|
|
101
|
+
{ 'instances' => [{ 'category' => 'hate', 'qualifiers' => %w[hate-symbol hate-nazi-symbols-public-display] }] }
|
|
102
102
|
when 'sexual.involving-minors' then { 'instances' => [{ 'category' => 'sexual', 'qualifiers' => ['sexual-minors'] }] }
|
|
103
103
|
when 'violence.graphic'
|
|
104
104
|
{ 'instances' => [{ 'category' => 'violence', 'qualifiers' => %w[violence-injury violence-graphic] }] }
|
data/lib/calmlens/testing.rb
CHANGED
|
@@ -27,8 +27,10 @@ module CalmLens
|
|
|
27
27
|
# grant (the default key has no grants), or the URL is signed: {CDN#sign_asset_url} returns
|
|
28
28
|
# `{cdn_base_url}/assets/{project_id}/sig_…`, a readable grant that only the mock's CDN accepts,
|
|
29
29
|
# which serves `original` only when signed with `original: true`. {#fetch_cdn} loads such a URL
|
|
30
|
-
# without the API key, as a browser would
|
|
31
|
-
#
|
|
30
|
+
# without the API key, as a browser would; without the key or a grant, a private asset answers
|
|
31
|
+
# as an unknown one does, `asset/not-found` (404) whatever its state, and only its placeholder is
|
|
32
|
+
# public. {#decide} applies a reviewer's decision, and {Assets#wait_for_decision} takes no real
|
|
33
|
+
# time: a pending asset times out at once.
|
|
32
34
|
# Filters join comparisons with `;` (and), which binds tighter than `,` (or), and group them with
|
|
33
35
|
# `( … )`; list cursors resume after the last item returned. {Assets#search} is the API's keyword
|
|
34
36
|
# search: every query word (case-insensitive, up to 8) must appear in a searchable field, and
|
|
@@ -47,12 +49,26 @@ module CalmLens
|
|
|
47
49
|
#
|
|
48
50
|
# `path` is relative to `base_url` for API calls (`/projects/<id>/assets`, without `/v1`) and to
|
|
49
51
|
# `cdn_base_url` for CDN requests, with each segment decoded. `query` holds the decoded query
|
|
50
|
-
# string
|
|
51
|
-
#
|
|
52
|
-
#
|
|
52
|
+
# string; an upload's holds only transport values (`wait`, `contentType`, sizes and part
|
|
53
|
+
# numbers). `body` is the bytes sent (streams are read), or nil: JSON text, including a text
|
|
54
|
+
# upload (`{name, ..., content: {plainText, type}}`), an upload form, or a later part's bytes.
|
|
55
|
+
# `status` is the HTTP status the mock answered with, or nil when the request failed without a
|
|
56
|
+
# response.
|
|
53
57
|
Request = Struct.new(:method, :url, :path, :query, :headers, :body, :status, keyword_init: true) do
|
|
54
58
|
# @return [Object] the body parsed as JSON.
|
|
55
59
|
def json = body && JSON.parse(body)
|
|
60
|
+
|
|
61
|
+
# An upload form's asset fields and file: `{'options' => Hash, 'file' => String}`, the file as
|
|
62
|
+
# UTF-8 text for a `text/*` content type and binary otherwise; nil for any other body.
|
|
63
|
+
# @return [Hash, nil]
|
|
64
|
+
def form
|
|
65
|
+
type = headers.find { |name, _| name.to_s.casecmp?('Content-Type') }&.last
|
|
66
|
+
options, file = Testing::FakeAPI.upload_form(body, type)
|
|
67
|
+
return nil unless options
|
|
68
|
+
|
|
69
|
+
text = query['contentType'].to_s.downcase.start_with?('text/')
|
|
70
|
+
{ 'options' => JSON.parse(options), 'file' => text ? file.dup.force_encoding(Encoding::UTF_8) : file }
|
|
71
|
+
end
|
|
56
72
|
end
|
|
57
73
|
|
|
58
74
|
# @return [Testing::State] the stored assets, folders, content and events.
|
|
@@ -64,12 +80,15 @@ module CalmLens
|
|
|
64
80
|
REQUEST_FAILURES = %w[transport-error timeout].freeze
|
|
65
81
|
# The methods {#fail_next_request} can target, by resource. Iterators are their list calls.
|
|
66
82
|
OPERATIONS = { assets: %i[search list retrieve list_children list_timeline update create classify upload_part move
|
|
67
|
-
retry explain wait_for_decision remove_content delete batch],
|
|
83
|
+
retry explain decide wait_for_decision remove_content delete batch],
|
|
68
84
|
folders: %i[list retrieve create update delete move],
|
|
69
85
|
hash_lists: %i[list retrieve create update delete list_entries add_entry remove_entry],
|
|
70
86
|
policies: %i[list retrieve retrieve_assigned create update reset upgrade set_automatic_updates delete
|
|
71
87
|
assign],
|
|
72
88
|
lenses: %i[list create retrieve update delete],
|
|
89
|
+
upload_presets: %i[list ensure delete],
|
|
90
|
+
project_settings: %i[get update],
|
|
91
|
+
metadata_fields: %i[list ensure delete],
|
|
73
92
|
text: %i[classify], events: %i[list],
|
|
74
93
|
cdn: %i[serve sign_asset_url get_asset_info] }.freeze
|
|
75
94
|
private_constant :OPERATIONS
|
|
@@ -183,8 +202,9 @@ module CalmLens
|
|
|
183
202
|
|
|
184
203
|
# Applies a reviewer's decision to a stored asset, as the portal or API would: the moderation
|
|
185
204
|
# status changes (`purged` also removes the content), the review joins `manualReviews` with
|
|
186
|
-
# `decidedBy` the mock API key (`{ 'type' => 'api-key', 'id' => nil }`) and `applied: true`,
|
|
187
|
-
# `asset.<decision>` event whose `actor` is a `user` joins the events feed
|
|
205
|
+
# `decidedBy` the mock API key (`{ 'type' => 'api-key', 'id' => nil }`) and `applied: true`, an
|
|
206
|
+
# `asset.<decision>` event whose `actor` is a `user` joins the events feed, and a waiting
|
|
207
|
+
# `pendingReview` is settled ({Assets#decide} leaves it waiting, as with the API).
|
|
188
208
|
# @param asset_id [String]
|
|
189
209
|
# @param decision [String] `allowed`, `limited`, `age-restricted`, `blocked` or `purged`
|
|
190
210
|
# @return [Hash] the asset
|
|
@@ -254,7 +274,8 @@ module CalmLens
|
|
|
254
274
|
# Fetches a URL of the mock's CDN as a browser would, with a GET and without the API key: a signed
|
|
255
275
|
# asset URL from {CDN#sign_asset_url} (with a resource appended), or a public one from
|
|
256
276
|
# {CDN#get_asset_url}. The request is recorded like any other. Error statuses are returned, not
|
|
257
|
-
# raised.
|
|
277
|
+
# raised. A private asset without a grant answers `asset/not-found` (404) whatever its state,
|
|
278
|
+
# except for its public placeholder.
|
|
258
279
|
# @param url [String]
|
|
259
280
|
# @return [Response] with its body read
|
|
260
281
|
def fetch_cdn(url)
|
|
@@ -46,20 +46,6 @@ module CalmLens
|
|
|
46
46
|
"blur": {
|
|
47
47
|
"type": "number"
|
|
48
48
|
},
|
|
49
|
-
"redact": {
|
|
50
|
-
"type": "object",
|
|
51
|
-
"properties": {
|
|
52
|
-
"text": {
|
|
53
|
-
"type": "boolean"
|
|
54
|
-
},
|
|
55
|
-
"personalInformation": {
|
|
56
|
-
"type": "boolean"
|
|
57
|
-
},
|
|
58
|
-
"faces": {
|
|
59
|
-
"type": "boolean"
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
49
|
"border": {
|
|
64
50
|
"type": "object",
|
|
65
51
|
"properties": {
|
data/lib/calmlens/transport.rb
CHANGED
|
@@ -19,7 +19,10 @@ module CalmLens
|
|
|
19
19
|
# @!attribute open_timeout [Numeric] seconds allowed to open a connection.
|
|
20
20
|
# @!attribute timeout [Numeric] seconds allowed for each read or write; not a whole-transfer deadline.
|
|
21
21
|
Request = Struct.new(:method, :url, :headers, :body, :open_timeout, :timeout, keyword_init: true) do
|
|
22
|
+
# Struct's own `to_s` and `pretty_print` would show the headers, which hold the API key.
|
|
22
23
|
def inspect = "#<#{self.class.name} #{method} #{url}>"
|
|
24
|
+
def to_s = inspect
|
|
25
|
+
def pretty_print(printer) = printer.text(inspect)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# Exceptions that mean the connection failed before a complete response arrived.
|
data/lib/calmlens/upload_body.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
3
5
|
module CalmLens
|
|
4
6
|
# An upload payload: a binary String, or an IO streamed with an exact byte count (or, when its
|
|
5
7
|
# length is unknown, until it ends, sent with chunked transfer encoding).
|
|
@@ -98,6 +100,20 @@ module CalmLens
|
|
|
98
100
|
UploadBody.new(@source.slice(offset, length))
|
|
99
101
|
end
|
|
100
102
|
|
|
103
|
+
# This body as the `file` part of an upload form, after an `options` part holding
|
|
104
|
+
# `options_json`: `multipart/form-data` with a random boundary. A stream stays a stream, read
|
|
105
|
+
# as the form is sent, and the form can be sent again whenever this body can.
|
|
106
|
+
# @return [Array(UploadBody, String)] the form and its Content-Type.
|
|
107
|
+
def to_form(options_json, type)
|
|
108
|
+
boundary = "calmlens-#{SecureRandom.uuid}"
|
|
109
|
+
head = "--#{boundary}\r\nContent-Disposition: form-data; name=\"options\"\r\nContent-Type: application/json\r\n\r\n".b +
|
|
110
|
+
options_json.b + "\r\n--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"\r\n".b +
|
|
111
|
+
"Content-Type: #{type.delete("\r\n")}\r\n\r\n".b
|
|
112
|
+
tail = "\r\n--#{boundary}--\r\n".b
|
|
113
|
+
source = @source.is_a?(String) ? head + @source + tail : FormReader.new(head, @source, tail)
|
|
114
|
+
[UploadBody.new(source), "multipart/form-data; boundary=#{boundary}"]
|
|
115
|
+
end
|
|
116
|
+
|
|
101
117
|
# The bytes, read fully. Used only by the offline mock.
|
|
102
118
|
def read_all
|
|
103
119
|
return @source if @source.is_a?(String)
|
|
@@ -111,9 +127,12 @@ module CalmLens
|
|
|
111
127
|
class IOReader
|
|
112
128
|
attr_reader :size
|
|
113
129
|
|
|
114
|
-
|
|
130
|
+
# @param bounded [Boolean] the stream goes on after `size` bytes (a slice of a larger file), so
|
|
131
|
+
# reading stops there without checking for more.
|
|
132
|
+
def initialize(io, size, bounded: false)
|
|
115
133
|
@io = io
|
|
116
134
|
@size = size
|
|
135
|
+
@bounded = bounded
|
|
117
136
|
@remaining = size
|
|
118
137
|
@start = (io.pos if io.respond_to?(:pos) && io.respond_to?(:seek))
|
|
119
138
|
rescue IOError, SystemCallError
|
|
@@ -125,7 +144,7 @@ module CalmLens
|
|
|
125
144
|
# A reader of `length` bytes from `offset` of this rewindable stream, positioned there.
|
|
126
145
|
def slice(offset, length)
|
|
127
146
|
@io.seek(@start + offset)
|
|
128
|
-
IOReader.new(@io, length)
|
|
147
|
+
IOReader.new(@io, length, bounded: true)
|
|
129
148
|
end
|
|
130
149
|
|
|
131
150
|
def rewind
|
|
@@ -138,6 +157,8 @@ module CalmLens
|
|
|
138
157
|
return read_unsized(length, buffer) if @size.nil?
|
|
139
158
|
|
|
140
159
|
if @remaining.zero?
|
|
160
|
+
return length.nil? ? ''.b : buffer&.clear && nil if @bounded
|
|
161
|
+
|
|
141
162
|
extra = @io.read(1)
|
|
142
163
|
unless extra.nil? || extra.empty?
|
|
143
164
|
raise Internal.invalid('The stream holds more bytes than sizeInBytes',
|
|
@@ -169,6 +190,68 @@ module CalmLens
|
|
|
169
190
|
read(length, buffer) || raise(EOFError)
|
|
170
191
|
end
|
|
171
192
|
end
|
|
193
|
+
|
|
194
|
+
# An upload form around a streamed file: the form's head, the file's bytes as an {IOReader}
|
|
195
|
+
# reads them, then its closing boundary.
|
|
196
|
+
class FormReader
|
|
197
|
+
def initialize(head, file, tail)
|
|
198
|
+
@head = head
|
|
199
|
+
@file = file
|
|
200
|
+
@tail = tail
|
|
201
|
+
@size = file.size && (head.bytesize + file.size + tail.bytesize)
|
|
202
|
+
rewind_pieces
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
attr_reader :size
|
|
206
|
+
|
|
207
|
+
def rewindable? = @file.rewindable?
|
|
208
|
+
|
|
209
|
+
def rewind
|
|
210
|
+
@file.rewind
|
|
211
|
+
rewind_pieces
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Reads like IO#read: everything left without a length, else up to `length` bytes, nil at the end.
|
|
215
|
+
def read(length = nil, buffer = nil)
|
|
216
|
+
return (buffer ? buffer.replace(''.b) : ''.b) if length&.zero?
|
|
217
|
+
|
|
218
|
+
output = ''.b
|
|
219
|
+
until @pieces.empty? || (length && output.bytesize >= length)
|
|
220
|
+
chunk = read_piece(length && (length - output.bytesize))
|
|
221
|
+
chunk.nil? || chunk.empty? ? next_piece : output << chunk
|
|
222
|
+
end
|
|
223
|
+
return buffer&.clear && nil if output.empty? && length
|
|
224
|
+
|
|
225
|
+
buffer ? buffer.replace(output) : output
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def readpartial(length, buffer = nil)
|
|
229
|
+
read(length, buffer) || raise(EOFError)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private
|
|
233
|
+
|
|
234
|
+
def rewind_pieces
|
|
235
|
+
@pieces = %i[head file tail]
|
|
236
|
+
@offset = 0
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def next_piece
|
|
240
|
+
@pieces.shift
|
|
241
|
+
@offset = 0
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Up to `length` bytes (all, without one) of the current piece; empty or nil once it is used up.
|
|
245
|
+
def read_piece(length)
|
|
246
|
+
piece = @pieces.first
|
|
247
|
+
return @file.read(length) if piece == :file
|
|
248
|
+
|
|
249
|
+
text = piece == :head ? @head : @tail
|
|
250
|
+
chunk = text.byteslice(@offset, length || text.bytesize).to_s
|
|
251
|
+
@offset += chunk.bytesize
|
|
252
|
+
chunk
|
|
253
|
+
end
|
|
254
|
+
end
|
|
172
255
|
end
|
|
173
256
|
private_constant :UploadBody
|
|
174
257
|
end
|
data/lib/calmlens/version.rb
CHANGED
data/lib/calmlens.rb
CHANGED
|
@@ -29,5 +29,6 @@ require_relative 'calmlens/upload_tokens'
|
|
|
29
29
|
require_relative 'calmlens/events'
|
|
30
30
|
require_relative 'calmlens/cdn'
|
|
31
31
|
require_relative 'calmlens/lenses'
|
|
32
|
+
require_relative 'calmlens/project_configuration'
|
|
32
33
|
require_relative 'calmlens/server_client'
|
|
33
34
|
require_relative 'calmlens/webhooks'
|
data/sig/calmlens.rbs
CHANGED
|
@@ -31,6 +31,7 @@ module CalmLens
|
|
|
31
31
|
?headers: Hash[String, String], ?request_id: String?) -> void
|
|
32
32
|
# Whole seconds from the `Retry-After` header.
|
|
33
33
|
def retry_after: () -> Integer?
|
|
34
|
+
def local?: () -> bool
|
|
34
35
|
def not_found?: () -> bool
|
|
35
36
|
def retryable?: () -> bool
|
|
36
37
|
def self.build: (String code, ?String? message, ?status: Integer?, ?details: Hash[String, json_value]?,
|
|
@@ -122,6 +123,9 @@ module CalmLens
|
|
|
122
123
|
attr_reader events: Events
|
|
123
124
|
attr_reader cdn: CDN
|
|
124
125
|
attr_reader lenses: Lenses
|
|
126
|
+
attr_reader upload_presets: UploadPresets
|
|
127
|
+
attr_reader project_settings: ProjectSettings
|
|
128
|
+
attr_reader metadata_fields: MetadataFields
|
|
125
129
|
attr_reader project_id: String
|
|
126
130
|
attr_reader base_url: String
|
|
127
131
|
attr_reader cdn_base_url: String
|
|
@@ -237,6 +241,25 @@ module CalmLens
|
|
|
237
241
|
def delete: (lens_id_options options) -> nil
|
|
238
242
|
end
|
|
239
243
|
|
|
244
|
+
class UploadPresets < Resource
|
|
245
|
+
def list: (?list_upload_presets_options options) -> upload_preset_page
|
|
246
|
+
def each: (?list_upload_presets_options options) { (upload_preset) -> void } -> nil
|
|
247
|
+
| (?list_upload_presets_options options) -> Enumerator[upload_preset, nil]
|
|
248
|
+
def ensure: (ensure_upload_presets_options options) -> ensure_upload_presets_response
|
|
249
|
+
def delete: (upload_preset_id_options options) -> nil
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class ProjectSettings < Resource
|
|
253
|
+
def get: (?request_only_options options) -> project_settings_resource
|
|
254
|
+
def update: (update_project_settings_options options) -> project_settings_resource
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
class MetadataFields < Resource
|
|
258
|
+
def list: (?request_only_options options) -> metadata_fields_resource
|
|
259
|
+
def ensure: (ensure_metadata_fields_options options) -> metadata_fields_resource
|
|
260
|
+
def delete: (delete_metadata_field_options options) -> nil
|
|
261
|
+
end
|
|
262
|
+
|
|
240
263
|
class UploadTokens < Resource
|
|
241
264
|
TOKEN_PREFIX: String
|
|
242
265
|
SIGNING_CONTEXT: String
|
|
@@ -379,6 +402,8 @@ module CalmLens
|
|
|
379
402
|
# The HTTP status the mock answered with; nil when the request failed without a response.
|
|
380
403
|
attr_accessor status: Integer?
|
|
381
404
|
def json: () -> json_value
|
|
405
|
+
# An upload form's `options` (parsed) and `file` (its bytes); nil for any other body.
|
|
406
|
+
def form: () -> { 'options' => Hash[String, json_value], 'file' => String }?
|
|
382
407
|
end
|
|
383
408
|
|
|
384
409
|
interface _Clock
|
|
@@ -445,8 +470,11 @@ module CalmLens
|
|
|
445
470
|
# Flags `category` with a score from 0 to 1 and optional qualifier scores.
|
|
446
471
|
def flag: (String | Symbol category, Numeric score, ?Hash[String | Symbol, Numeric] qualifiers) -> self
|
|
447
472
|
def not_evaluated: (String | Symbol category) -> self
|
|
473
|
+
# Leaves the decision waiting for human review: `sampled`, or `uncertain` with the categories that made it so.
|
|
474
|
+
def pending_review: ("sampled" | "uncertain" | :sampled | :uncertain reason, ?Array[String | Symbol] uncertain_categories) -> self
|
|
448
475
|
# The `categories`, `score` and `policy` a decided asset gets; nil for `pending` and `failed`.
|
|
449
|
-
|
|
476
|
+
# `requested_at` is when a pending review joined the queue, in epoch milliseconds.
|
|
477
|
+
def report: (?Integer requested_at) -> Hash[String, json_value]?
|
|
450
478
|
end
|
|
451
479
|
|
|
452
480
|
# What MockServerClient's `moderate:` callback receives.
|
data/sig/error_codes.rbs
CHANGED
|
@@ -37,12 +37,14 @@ module CalmLens
|
|
|
37
37
|
PERMISSION_DENIED: String
|
|
38
38
|
UPLOAD_TOO_LARGE: String
|
|
39
39
|
DOCUMENT_TOO_MANY_ITEMS: String
|
|
40
|
+
MEDIA_DURATION_LIMIT: String
|
|
40
41
|
INVALID_IMAGE: String
|
|
41
42
|
IMAGE_TOO_LARGE: String
|
|
42
43
|
FREE_UPLOADS_EXHAUSTED: String
|
|
43
44
|
UPLOADS_PAUSED: String
|
|
44
45
|
UNSUPPORTED_UPLOAD_TYPE: String
|
|
45
46
|
INVALID_UPLOAD_PAYLOAD: String
|
|
47
|
+
UPLOAD_OPTIONS_IN_QUERY: String
|
|
46
48
|
USE_UPLOAD_PART: String
|
|
47
49
|
UPLOAD_NOT_OPEN: String
|
|
48
50
|
INVALID_UPLOAD_PART_NUMBER: String
|
|
@@ -99,6 +101,14 @@ module CalmLens
|
|
|
99
101
|
UPLOAD_PRESET_DAILY_LIMIT_REACHED: String
|
|
100
102
|
UPLOAD_PRESET_ORIGIN_NOT_ALLOWED: String
|
|
101
103
|
UPLOAD_PRESET_FIELD_NOT_ALLOWED: String
|
|
104
|
+
UPLOAD_PRESET_REVISION_CONFLICT: String
|
|
105
|
+
UPLOAD_PRESET_KEY_TAKEN: String
|
|
106
|
+
UPLOAD_PRESET_DAILY_LIMIT_REQUIRED: String
|
|
107
|
+
UPLOAD_PRESET_INVALID_FOLDER: String
|
|
108
|
+
UPLOAD_PRESET_INVALID_POLICY: String
|
|
109
|
+
PROJECT_SETTINGS_REVISION_CONFLICT: String
|
|
110
|
+
SEARCH_ESTIMATE_CHANGED: String
|
|
111
|
+
METADATA_FIELD_NOT_FOUND: String
|
|
102
112
|
UPLOAD_TOKEN_INVALID: String
|
|
103
113
|
UPLOAD_TOKEN_EXPIRED: String
|
|
104
114
|
UPLOAD_TOKEN_USED: String
|
data/sig/qualifiers.rbs
CHANGED
|
@@ -57,6 +57,7 @@ module CalmLens
|
|
|
57
57
|
?"sexual-educational" => qualifier_result,
|
|
58
58
|
?"sexual-discussion" => qualifier_result,
|
|
59
59
|
?"sexual-fictional" => qualifier_result,
|
|
60
|
+
?"sexual-artistic" => qualifier_result,
|
|
60
61
|
?"sexual-support-prevention" => qualifier_result,
|
|
61
62
|
?"violence-attack" => qualifier_result,
|
|
62
63
|
?"violence-threat" => qualifier_result,
|