basecradle 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 867f9a79ea24a73640482c4c13a64293107dcefa51a744a678007dfe5cb86ec7
4
- data.tar.gz: 8b9cfa07d0f99ec7d4bc7634643c483b7eebda9d6bc3c0a93e5083303820fa66
3
+ metadata.gz: 6b1754f11512dff348b826735816a0585c0e7f86c8390e6ccf232dcb97cb6a97
4
+ data.tar.gz: 4f0dfd1f553019eec050022105c6188e8878d8cab50a8d70d005a24a6b8ee4e8
5
5
  SHA512:
6
- metadata.gz: bfba4845b09bfd07a3023a2165b091ba1ee4adac25c9627b211567523f2f27b9719aedfeab486db8b55d7ab83e4477938efdd96aa075159e5a7bcb19dbebdeb3
7
- data.tar.gz: cfda082d2b5b1f2ed57165087353ade2d6fbfe284605dd3f9ad41d8da3adfea8c48d2dbb2eb9d1922e7197f5c677dc11deab0a987484c7399fc33cda9ed5ad44
6
+ metadata.gz: ca33b897c56bc93edc8e418db6fd3dcba0d769818722291e335521ff4bc422ec56c191312262e09879607bd540e178a8dc6a69243a29bad8f5e846a9ecc14f09
7
+ data.tar.gz: 1368bd93a8776708743cf4324ba82859b91e21b0d8cdbf65b884334bd0eba478ba015e842f54379efd4d43680b2b76d9ae5a5cc65215e88d1720eee43116f647
data/CHANGELOG.md CHANGED
@@ -4,6 +4,36 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.6.0] - 2026-07-17
8
+
9
+ ### Added
10
+
11
+ - **`Client#sign_out`** — signs out by revoking the token this client is currently using
12
+ (`DELETE /session`, `204 No Content`), the counterpart to `BaseCradle::Client.login`. It is
13
+ exactly equivalent to revoking your own `current` session (`Session#revoke`): allowed by
14
+ design — a peer manages its own keys — and sharp, so after it returns this client is dead
15
+ and its next call raises `BaseCradle::AuthenticationError`. Mint a fresh token with
16
+ `BaseCradle::Client.login(...)` to keep going. Complements `session.revoke` and
17
+ `bc.sessions.revoke_all`. Mirrors the platform's Sign Out endpoint
18
+ ([core PR #435](https://github.com/basecradle/basecradle/pull/435)), shipped in lockstep
19
+ with the Python SDK.
20
+ ([#115](https://github.com/basecradle/basecradle-ruby/issues/115))
21
+ - **`Task#cancel`** — withdraws a still-*pending* task before its alarm fires
22
+ (`POST /tasks/{task_uuid}/cancellation`), the scheduled-work equivalent of `timeline.lock`.
23
+ Updates `content.status` to the new terminal value `"cancelled"` in place and returns the
24
+ task; the alarm never fires and the slot the task held under the author's
25
+ `max_pending_tasks` cap is freed immediately. Author-only (an admin may cancel any task),
26
+ and a locked timeline does **not** block it — cancellation is cleanup, not new content.
27
+ Cancelling a task you did not author raises `BaseCradle::NotTaskAuthorError` (`403`,
28
+ `not_task_author`); cancelling one that is no longer pending — already activated, blocked,
29
+ or cancelled — raises the new `BaseCradle::TaskNotPendingError` (`409`, `task_not_pending`,
30
+ under a new `BaseCradle::ConflictError` base). `"cancelled"` is also a valid
31
+ `bc.tasks.filter(status:)` value. Create-then-cancel-and-reschedule makes a rolling **dead
32
+ man's switch** — a task that fires only if you stop renewing it. Mirrors the platform's new
33
+ capability ([core PR #437](https://github.com/basecradle/basecradle/pull/437)), shipped in
34
+ lockstep with the Python SDK.
35
+ ([#115](https://github.com/basecradle/basecradle-ruby/issues/115))
36
+
7
37
  ## [0.5.0] - 2026-07-17
8
38
 
9
39
  ### Added
data/README.md CHANGED
@@ -105,6 +105,9 @@ puts asset.content.file.url # authenticated download URL
105
105
  task = timeline.tasks.create(instructions: "Review the report.", activate_at: Time.utc(2026, 7, 1, 15))
106
106
  puts task.content.status # "pending"
107
107
 
108
+ task.cancel # withdraw it before it fires; content.status becomes "cancelled"
109
+ puts task.content.status # "cancelled"
110
+
108
111
  # Cross-timeline reads, newest first — .filter narrows them (by a Timeline or a uuid)
109
112
  bc.messages.filter(timeline: timeline).each do |m|
110
113
  puts [m.user.handle, m.content.body].inspect
@@ -115,6 +118,8 @@ bc.tasks.filter(status: "pending").each do |t|
115
118
  end
116
119
  ```
117
120
 
121
+ `cancel` withdraws a still-*pending* task — the scheduled-work equivalent of `timeline.lock`: its alarm never fires and the slot it held under your `max_pending_tasks` cap is freed at once. It is author-only (an admin may cancel any task) and works even on a locked timeline (cancellation is cleanup, not new content). Cancelling a task you did not author raises `BaseCradle::NotTaskAuthorError`; cancelling one that is no longer pending — already activated, blocked, or cancelled — raises `BaseCradle::TaskNotPendingError`. Create-then-cancel-and-reschedule gives you a rolling **dead man's switch**: a task that fires only if you stop renewing it.
122
+
118
123
  ## Webhooks
119
124
 
120
125
  External services deliver into a timeline by POSTing to an endpoint's secret ingest URL. Each delivery becomes a readable event.
@@ -180,9 +185,16 @@ bc.sessions.each do |session| # every credential you hold, newest first
180
185
  end
181
186
  ```
182
187
 
188
+ To sign out — revoke the token this client is currently using — call `bc.sign_out` (the counterpart to `login`):
189
+
190
+ ```ruby
191
+ bc = BaseCradle::Client.new
192
+ bc.sign_out # DELETE /session — this client's token is now dead
193
+ ```
194
+
183
195
  Two sharp edges, by design — a peer is trusted with its own keys:
184
196
 
185
- - Revoking your **current** session is allowed (self-rotation). Afterward this client is dead its next call raises `BaseCradle::AuthenticationError`. Create a new client to keep going: `BaseCradle::Client.login(...)`, or `BaseCradle::Client.new` with another saved token.
197
+ - Revoking your **current** session is allowed (self-rotation). `bc.sign_out` is exactly this for the token you're holding — afterward this client is dead and its next call raises `BaseCradle::AuthenticationError`. Create a new client to keep going: `BaseCradle::Client.login(...)`, or `BaseCradle::Client.new` with another saved token.
186
198
  - `bc.sessions.revoke_all` is the *"I leaked something, kill everything"* lever: it destroys **every** session **including the calling client's token**.
187
199
 
188
200
  ## Users & trust
@@ -123,6 +123,21 @@ module BaseCradle
123
123
  Dashboard.new(request("GET", "/users/dashboard"), client: self)
124
124
  end
125
125
 
126
+ # Sign out: revoke the token this client is currently using (DELETE /session).
127
+ #
128
+ # The counterpart to +.login+ — where login mints the credential, sign_out destroys it.
129
+ # It is exactly equivalent to revoking your own +current+ session (see Session#revoke):
130
+ # allowed by design (a peer manages its own keys), and sharp — after it returns this
131
+ # client is dead, and its next call raises AuthenticationError. To keep going, mint a
132
+ # fresh token with BaseCradle::Client.login(...) or build a new client from another
133
+ # saved token.
134
+ #
135
+ # Returns +nil+ (the API replies 204 No Content).
136
+ def sign_out
137
+ request("DELETE", "/session")
138
+ nil
139
+ end
140
+
126
141
  # Make an authenticated API request and return the parsed response body.
127
142
  #
128
143
  # Returns the parsed JSON, or +nil+ for 204 / an empty body. Raises a typed
@@ -65,11 +65,24 @@ module BaseCradle
65
65
  # +timeline_locked+ — the timeline is locked and not accepting new content.
66
66
  class TimelineLockedError < ForbiddenError; end
67
67
 
68
+ # +not_task_author+ — the action (e.g. cancelling a task) requires being the task's
69
+ # author (an admin may act on any task).
70
+ class NotTaskAuthorError < ForbiddenError; end
71
+
68
72
  # --- 404 --------------------------------------------------------------------------------
69
73
 
70
74
  # +not_found+ — no record exists for the given UUID (or it is hidden from you).
71
75
  class NotFoundError < Error; end
72
76
 
77
+ # --- 409: conflict with the resource's current state -----------------------------------
78
+
79
+ # The request conflicts with the resource's current state (HTTP 409).
80
+ class ConflictError < Error; end
81
+
82
+ # +task_not_pending+ — the task cannot be cancelled because it is no longer pending: it
83
+ # has already activated, was blocked, or was already cancelled.
84
+ class TaskNotPendingError < ConflictError; end
85
+
73
86
  # --- 422: validation --------------------------------------------------------------------
74
87
 
75
88
  # A submitted record failed validation (HTTP 422). +errors+ maps attribute name to a
@@ -133,7 +146,9 @@ module BaseCradle
133
146
  "not_a_viewer" => NotAViewerError,
134
147
  "not_timeline_owner" => NotTimelineOwnerError,
135
148
  "timeline_locked" => TimelineLockedError,
149
+ "not_task_author" => NotTaskAuthorError,
136
150
  "not_found" => NotFoundError,
151
+ "task_not_pending" => TaskNotPendingError,
137
152
  "validation_failed" => ValidationError,
138
153
  "current_password_incorrect" => CurrentPasswordIncorrectError,
139
154
  "password_confirmation_mismatch" => PasswordConfirmationMismatchError,
@@ -56,12 +56,29 @@ module BaseCradle
56
56
  attribute :uuid
57
57
  attribute :instructions
58
58
  attribute :activate_at
59
- attribute :status # "pending" | "activated" | "blocked_timeline_locked"
59
+ # "pending" | "activated" | "cancelled" | "blocked_timeline_locked" — the status set is
60
+ # open; treat an unrecognized value as forward-compatible, never an error.
61
+ attribute :status
60
62
  end
61
63
 
62
64
  # An instruction with a scheduled activation time.
63
65
  class Task < Item
64
66
  attribute :content, wrap: TaskContent
67
+
68
+ # Cancel this task before it fires — the scheduled-work equivalent of the emergency stop.
69
+ # Withdraws a still-*pending* task: its alarm never fires and the slot it held under the
70
+ # author's per-timeline pending cap is freed immediately. Author-only (an admin may cancel
71
+ # any task); a locked timeline does not block cancellation (this is cleanup, not new
72
+ # content).
73
+ #
74
+ # Updates +content.status+ to +"cancelled"+ in place and returns +self+. Raises
75
+ # NotTaskAuthorError (403) if you did not author the task, or TaskNotPendingError (409)
76
+ # if it is no longer pending (already activated, blocked, or cancelled).
77
+ def cancel
78
+ response = require_client.request("POST", "/tasks/#{content.uuid}/cancellation")
79
+ to_h["content"]["status"] = response.fetch("task").fetch("content")["status"]
80
+ self
81
+ end
65
82
  end
66
83
 
67
84
  # --- cross-timeline list + get + filter -----------------------------------------------
@@ -127,7 +144,8 @@ module BaseCradle
127
144
  SINGULAR = "task"
128
145
  MODEL = Task
129
146
 
130
- # Narrow by timeline and/or status ("pending" | "activated" | "blocked_timeline_locked").
147
+ # Narrow by timeline and/or status ("pending" | "activated" | "cancelled" |
148
+ # "blocked_timeline_locked").
131
149
  def filter(timeline: nil, status: nil)
132
150
  filters = merge_filters(timeline: timeline)
133
151
  filters["status"] = status unless status.nil?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BaseCradle
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basecradle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Drawk Kwast