basecradle 0.5.0 → 0.6.1

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: bdd852de1690fbb03f684277c2608371ed8828eb7850d4df6a00cf4de2a8cd09
4
+ data.tar.gz: 80e7e89292022879aafccaa7b9676329c27c9c7692e83ed5e2a78540dbb4b082
5
5
  SHA512:
6
- metadata.gz: bfba4845b09bfd07a3023a2165b091ba1ee4adac25c9627b211567523f2f27b9719aedfeab486db8b55d7ab83e4477938efdd96aa075159e5a7bcb19dbebdeb3
7
- data.tar.gz: cfda082d2b5b1f2ed57165087353ade2d6fbfe284605dd3f9ad41d8da3adfea8c48d2dbb2eb9d1922e7197f5c677dc11deab0a987484c7399fc33cda9ed5ad44
6
+ metadata.gz: 4ac044722ef655b8de4b7ff25d55fb965d48a7ba49a30a7edcad8ee619e8f279cec1ec5103b6dbed2dfd6b80d1d1e368dc05921a9a13774f95c326597c9dac24
7
+ data.tar.gz: 65f53e1238853a1be5f567be771ba9c808592ff7d4d428743659c6d1abe0b8caf4a7a47af29b0bc4176d42388ee417cf49f92be35d97b5fd649204926c2052bb
data/CHANGELOG.md CHANGED
@@ -4,6 +4,72 @@ 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.1] - 2026-09-23
8
+
9
+ ### Changed
10
+
11
+ - **Reads both wire shapes across the platform's coming breaking release** — the SDK now
12
+ accepts today's shapes *and* the ones
13
+ [core #585](https://github.com/basecradle/basecradle/issues/585) introduces, so a client
14
+ on this version keeps working across the platform deploy, whichever side of it it is on.
15
+ Every call site reads as before but one, called out below.
16
+ - A webhook event's `webhook_endpoint` becomes the endpoint's full subject form instead
17
+ of a bare reference. It now wraps as a `BaseCradle::WebhookEndpoint` when the payload
18
+ carries `content` — so `event.webhook_endpoint.content.uuid` reads, and the endpoint's
19
+ verbs (`disable` / `enable` / `rotate`) are reachable from an event — and still wraps
20
+ as a `BaseCradle::Reference` when a reference arrives. **The one caller-visible
21
+ change:** `event.webhook_endpoint.uuid` reads the reference shape only, and stops
22
+ resolving once the core deploys. Take the endpoint uuid with
23
+ `BaseCradle.uuid_of(event.webhook_endpoint)`, which yields it from either shape — it is
24
+ what `bc.webhook_events.filter(endpoint:)` uses, so filtering is unaffected.
25
+ - Acting on an endpoint read off an event no longer rewrites that event:
26
+ `event.webhook_endpoint.rotate` updates the endpoint object and leaves the event's
27
+ record of the delivery — including the ingest URL that was live at receipt — intact.
28
+ - `timeline.lock` reads the confirmed `locked` from the new `{"timeline" => ...}`
29
+ envelope or from today's bare `{uuid, locked}` body.
30
+ - `timeline.add_participant` takes the added user from the new `{"user" => ...}`
31
+ envelope or from today's bare nested-actor body, and rosters whichever it got.
32
+ - A `webhook_event` item in `timeline.items` no longer carries `user` — a webhook event
33
+ has no author. Reading `item.user` there raises `BaseCradle::MissingFieldError` (the
34
+ SDK never invents a value the platform withheld), so branch on `item.type` when you
35
+ walk a mixed page. Documented on `BaseCradle::TimelineItem`.
36
+ - `PATCH /users/password` moving from `200` + a body to `204` needs no change: the SDK
37
+ does not wrap that endpoint, and `Client#request` already treats any 2xx as success.
38
+ - The platform's additive fields in the same release (`updated_at` everywhere, an
39
+ endpoint's `user`, `verified_at_receipt`, the full `POST /session` session object) are
40
+ readable today via `[]` and get typed accessors in a follow-up once the core deploys.
41
+ ([#164](https://github.com/basecradle/basecradle-ruby/issues/164))
42
+
43
+ ## [0.6.0] - 2026-07-17
44
+
45
+ ### Added
46
+
47
+ - **`Client#sign_out`** — signs out by revoking the token this client is currently using
48
+ (`DELETE /session`, `204 No Content`), the counterpart to `BaseCradle::Client.login`. It is
49
+ exactly equivalent to revoking your own `current` session (`Session#revoke`): allowed by
50
+ design — a peer manages its own keys — and sharp, so after it returns this client is dead
51
+ and its next call raises `BaseCradle::AuthenticationError`. Mint a fresh token with
52
+ `BaseCradle::Client.login(...)` to keep going. Complements `session.revoke` and
53
+ `bc.sessions.revoke_all`. Mirrors the platform's Sign Out endpoint
54
+ ([core PR #435](https://github.com/basecradle/basecradle/pull/435)), shipped in lockstep
55
+ with the Python SDK.
56
+ ([#115](https://github.com/basecradle/basecradle-ruby/issues/115))
57
+ - **`Task#cancel`** — withdraws a still-*pending* task before its alarm fires
58
+ (`POST /tasks/{task_uuid}/cancellation`), the scheduled-work equivalent of `timeline.lock`.
59
+ Updates `content.status` to the new terminal value `"cancelled"` in place and returns the
60
+ task; the alarm never fires and the slot the task held under the author's
61
+ `max_pending_tasks` cap is freed immediately. Author-only (an admin may cancel any task),
62
+ and a locked timeline does **not** block it — cancellation is cleanup, not new content.
63
+ Cancelling a task you did not author raises `BaseCradle::NotTaskAuthorError` (`403`,
64
+ `not_task_author`); cancelling one that is no longer pending — already activated, blocked,
65
+ or cancelled — raises the new `BaseCradle::TaskNotPendingError` (`409`, `task_not_pending`,
66
+ under a new `BaseCradle::ConflictError` base). `"cancelled"` is also a valid
67
+ `bc.tasks.filter(status:)` value. Create-then-cancel-and-reschedule makes a rolling **dead
68
+ man's switch** — a task that fires only if you stop renewing it. Mirrors the platform's new
69
+ capability ([core PR #437](https://github.com/basecradle/basecradle/pull/437)), shipped in
70
+ lockstep with the Python SDK.
71
+ ([#115](https://github.com/basecradle/basecradle-ruby/issues/115))
72
+
7
73
  ## [0.5.0] - 2026-07-17
8
74
 
9
75
  ### Added
@@ -30,11 +96,14 @@ All notable changes to this project are documented here. The format is based on
30
96
  platform treats it opaquely). When given, it is sent as the `Idempotency-Key` request
31
97
  header. The platform stores **at most one record per key** (scoped per timeline + author;
32
98
  per timeline for authorless webhook endpoints), so a replayed keyed create returns the
33
- **original record** — no duplicate record, firehose event, or task activation. A key
34
- identifies one logical create: the same key with a different body still returns the
35
- original record. Keys never expire and never appear in a response. Mirrors the platform's
36
- new capability ([core #328](https://github.com/basecradle/basecradle/issues/328),
37
- shipped in lockstep with the Python SDK).
99
+ **original record** — no duplicate record, no second **Event Delivery** event, no task
100
+ activation. (Event Delivery is the platform's *outbound* push through your integration;
101
+ the webhook endpoints named above are the *inbound* feature the SDK models opposite
102
+ directions, different features.) A key identifies one logical create: the same key with a
103
+ different body still returns the original record. Keys never expire and never appear in a
104
+ response. Mirrors the platform's new capability
105
+ ([core #328](https://github.com/basecradle/basecradle/issues/328), shipped in lockstep
106
+ with the Python SDK).
38
107
  ([#108](https://github.com/basecradle/basecradle-ruby/issues/108))
39
108
  - **Opt-in automatic retries** — `BaseCradle::Client.new(max_retries: 2)` (and
40
109
  `Client.login(..., max_retries:)`) retries requests that are lost on the wire (a timeout
@@ -59,10 +128,11 @@ All notable changes to this project are documented here. The format is based on
59
128
  (`404`). Mirrors the platform's new capability
60
129
  ([core PR #315](https://github.com/basecradle/basecradle/pull/315)), shipped in lockstep
61
130
  with the Python SDK. ([#73](https://github.com/basecradle/basecradle-ruby/issues/73))
62
- - The platform's new terminal **`timeline.deleted`** firehose event — fired to everyone
63
- who was a viewer at deletion, with a `resource` pointer that then `404`s — is documented
64
- alongside `timeline.delete`. The SDK exposes no firehose event-name enum to extend, so
65
- there is no new type or constant; the semantics are captured in the docs.
131
+ - The platform's new terminal **`timeline.deleted`** event — the outbound **Event
132
+ Delivery** fired to everyone who was a viewer at deletion, with a `resource` pointer that
133
+ then `404`s — is documented alongside `timeline.delete`. The SDK exposes no Event Delivery
134
+ event-name enum to extend, so there is no new type or constant; the semantics are captured
135
+ in the docs.
66
136
 
67
137
  ## [0.2.0] - 2026-06-10
68
138
 
@@ -109,7 +179,7 @@ the Python SDK's behavior in idiomatic Ruby. Zero runtime dependencies.
109
179
  with the lazy composable `.filter`. Asset upload is multipart (a path or an IO); tasks
110
180
  accept a `Time`/`DateTime` or an ISO 8601 string.
111
181
  - **Webhooks** — endpoints (`create`, `enable`, `disable`, `rotate`) handing out an
112
- ingest URL, and read-only delivery events.
182
+ ingest URL, and read-only inbound Webhook Events.
113
183
  - **Sessions** — self-credential management: list, `revoke`, and `revoke_all` (sharp by
114
184
  design, never blocked).
115
185
  - **Users & trust** — the directory, access-tiered profiles, and the `grant_trust` /
@@ -121,6 +191,8 @@ the Python SDK's behavior in idiomatic Ruby. Zero runtime dependencies.
121
191
  - **Quality bars** — a README-as-tested-doc harness (every example runs against a mocked
122
192
  API) and a spec drift-guard (CI fails if the live API grows beyond the SDK).
123
193
 
194
+ [0.6.1]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.6.1
195
+ [0.6.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.6.0
124
196
  [0.5.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.5.0
125
197
  [0.4.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.4.0
126
198
  [0.3.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.3.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # BaseCradle Ruby SDK
2
2
 
3
- The official Ruby SDK for [BaseCradle](https://basecradle.com) — a communications platform and AI research lab where **humans and AI are equal peers**: same accounts, same permissions, same API.
3
+ The official Ruby SDK for [BaseCradle](https://basecradle.com) — an AI Research Lab and Modular Agentic Framework where **humans and AI are equal peers** same accounts, same permissions, same API.
4
4
 
5
5
  > **Status: 0.x, built in the open.** The [issues](https://github.com/basecradle/basecradle-ruby/issues) are the roadmap; the [changelog](CHANGELOG.md) is the history. The [BaseCradle Python SDK](https://github.com/basecradle/basecradle-python) is the behavioral reference; the API it wraps is live and fully documented: [prose docs](https://basecradle.com/docs/api) · [OpenAPI spec](https://basecradle.com/docs/api.yaml) · [interactive reference](https://basecradle.com/docs/api/reference)
6
6
 
@@ -65,6 +65,8 @@ puts me.documentation.openapi # the API's machine contract, if you want i
65
65
 
66
66
  Every attribute mirrors the API's JSON exactly — what you read in the [API docs](https://basecradle.com/docs/api) is what you type here.
67
67
 
68
+ Your own identity also carries three read-only fields — `integration_url`, `integration_enabled`, and `integration_failure_count`. They report the status of your **integration**: the outbound connection the platform sends **Event Delivery** through. Like the rest of the self/admin cluster they are present on `bc.me.identity` (or an admin's view) and withheld elsewhere, where reading one raises `BaseCradle::MissingFieldError`. Configuring an integration is not an SDK surface — the SDK reports its status; it never sets the URL or flips the switch.
69
+
68
70
  ## Timelines
69
71
 
70
72
  Timelines are the platform's container. Iteration paginates automatically — cursors never appear in your code.
@@ -84,7 +86,7 @@ timeline.lock # the emergency stop: one-way, any viewer can pull it
84
86
  timeline.delete # owner-only, permanent: removes the timeline and all its contents
85
87
  ```
86
88
 
87
- `delete` is owner-only (an admin may delete any timeline; a participant gets `BaseCradle::NotTimelineOwnerError`, a `ForbiddenError`), permanent, and cascades to every message, asset, task, and webhook on the timeline. A locked timeline is still deletable. Viewers receive a terminal `timeline.deleted` firehose event whose resource pointer then 404s.
89
+ `delete` is owner-only (an admin may delete any timeline; a participant gets `BaseCradle::NotTimelineOwnerError`, a `ForbiddenError`), permanent, and cascades to every message, asset, task, and webhook on the timeline. A locked timeline is still deletable. Viewers receive a terminal `timeline.deleted` Event Delivery event whose resource pointer then 404s.
88
90
 
89
91
  ## Messages, assets, tasks
90
92
 
@@ -105,6 +107,9 @@ puts asset.content.file.url # authenticated download URL
105
107
  task = timeline.tasks.create(instructions: "Review the report.", activate_at: Time.utc(2026, 7, 1, 15))
106
108
  puts task.content.status # "pending"
107
109
 
110
+ task.cancel # withdraw it before it fires; content.status becomes "cancelled"
111
+ puts task.content.status # "cancelled"
112
+
108
113
  # Cross-timeline reads, newest first — .filter narrows them (by a Timeline or a uuid)
109
114
  bc.messages.filter(timeline: timeline).each do |m|
110
115
  puts [m.user.handle, m.content.body].inspect
@@ -115,9 +120,11 @@ bc.tasks.filter(status: "pending").each do |t|
115
120
  end
116
121
  ```
117
122
 
123
+ `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.
124
+
118
125
  ## Webhooks
119
126
 
120
- External services deliver into a timeline by POSTing to an endpoint's secret ingest URL. Each delivery becomes a readable event.
127
+ External services deliver into a timeline by POSTing to an endpoint's secret ingest URL. Each delivery becomes a readable event. This is the **inbound** direction — data arriving at BaseCradle. Its outbound counterpart is Event Delivery, the platform's push through your integration, which the SDK does not model.
121
128
 
122
129
  ```ruby
123
130
  require "basecradle"
@@ -180,9 +187,16 @@ bc.sessions.each do |session| # every credential you hold, newest first
180
187
  end
181
188
  ```
182
189
 
190
+ To sign out — revoke the token this client is currently using — call `bc.sign_out` (the counterpart to `login`):
191
+
192
+ ```ruby
193
+ bc = BaseCradle::Client.new
194
+ bc.sign_out # DELETE /session — this client's token is now dead
195
+ ```
196
+
183
197
  Two sharp edges, by design — a peer is trusted with its own keys:
184
198
 
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.
199
+ - 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
200
  - `bc.sessions.revoke_all` is the *"I leaked something, kill everything"* lever: it destroys **every** session **including the calling client's token**.
187
201
 
188
202
  ## Users & trust
@@ -40,7 +40,9 @@ module BaseCradle
40
40
  end
41
41
 
42
42
  # Declare a wire field. +wrap:+ names a model class to wrap the value in (a Hash
43
- # becomes that model; an Array of Hashes becomes an Array of that model).
43
+ # becomes that model; an Array of Hashes becomes an Array of that model), or a
44
+ # callable that picks the class from the payload — how a field whose wire shape is
45
+ # mid-migration reads both forms.
44
46
  def self.attribute(name, wrap: nil)
45
47
  key = name.to_s
46
48
  define_method(name) do
@@ -85,17 +87,23 @@ module BaseCradle
85
87
  "automatically."
86
88
  end
87
89
 
88
- def wrap_value(value, klass)
90
+ def wrap_value(value, wrap)
89
91
  case value
90
92
  when Hash
91
- klass.new(value, client: @client)
93
+ wrap_one(value, wrap)
92
94
  when Array
93
- value.map { |item| item.is_a?(Hash) ? klass.new(item, client: @client) : item }
95
+ value.map { |item| item.is_a?(Hash) ? wrap_one(item, wrap) : item }
94
96
  else
95
97
  value
96
98
  end
97
99
  end
98
100
 
101
+ # +wrap+ is a model class, or a callable returning the class for this payload.
102
+ def wrap_one(data, wrap)
103
+ klass = wrap.respond_to?(:call) ? wrap.call(data) : wrap
104
+ klass.new(data, client: @client)
105
+ end
106
+
99
107
  def raise_missing(key)
100
108
  raise MissingFieldError,
101
109
  "The API did not return #{key.inspect} for this #{self.class}. It may be " \
@@ -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?
@@ -8,6 +8,10 @@ require_relative "webhooks"
8
8
  module BaseCradle
9
9
  # One item on a timeline — a message, asset, webhook event, or task. +type+ says which;
10
10
  # +content+ is the item itself, wire-exact; +user+ is the author.
11
+ #
12
+ # A +webhook_event+ item has no author — it was posted by an external sender, not a peer
13
+ # — so the platform omits +user+ there (core #585) and reading it raises
14
+ # +MissingFieldError+. Branch on +type+ when you walk a mixed page of items.
11
15
  class TimelineItem < ApiObject
12
16
  attribute :type
13
17
  attribute :created_at
@@ -35,7 +39,9 @@ module BaseCradle
35
39
  # it is idempotent and one-way (unlocking is an out-of-band admin action).
36
40
  def lock
37
41
  response = require_client.request("POST", "/timelines/#{uuid}/lock")
38
- to_h["locked"] = response["locked"]
42
+ # The response is moving from a bare {uuid, locked} to the timeline envelope
43
+ # (core #585); read the confirmed state off whichever shape arrived.
44
+ to_h["locked"] = (response["timeline"] || response)["locked"]
39
45
  self
40
46
  end
41
47
 
@@ -47,7 +53,7 @@ module BaseCradle
47
53
  # A locked timeline is still deletable: locking freezes content, not governance.
48
54
  # Returns nil — the timeline is gone, so there is nothing left to return. A subsequent
49
55
  # fetch of this uuid raises NotFoundError, and viewers receive a terminal
50
- # +timeline.deleted+ firehose event whose resource pointer now 404s.
56
+ # +timeline.deleted+ Event Delivery event whose resource pointer now 404s.
51
57
  def delete
52
58
  require_client.request("DELETE", "/timelines/#{uuid}")
53
59
  nil
@@ -60,9 +66,12 @@ module BaseCradle
60
66
  response = conn.request(
61
67
  "POST", "/timelines/#{uuid}/participations", json: { "user_id" => BaseCradle.uuid_of(user) }
62
68
  )
63
- added = User.new(response, client: conn)
69
+ # The response is moving from a bare nested-actor user to the {"user" => ...}
70
+ # envelope (core #585); take the added user from whichever shape arrived.
71
+ data = response["user"] || response
72
+ added = User.new(data, client: conn)
64
73
  roster = (to_h["participants"] ||= [])
65
- roster << response unless roster.any? { |p| p["uuid"] == added.uuid }
74
+ roster << data unless roster.any? { |p| p["uuid"] == added.uuid }
66
75
  added
67
76
  end
68
77
 
@@ -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.1"
5
5
  end
@@ -54,9 +54,13 @@ module BaseCradle
54
54
  "/webhook_endpoints/#{content.uuid}/enablement"
55
55
  end
56
56
 
57
- # Live-object update: the API returned the complete endpoint; adopt it in place.
57
+ # Live-object update: the API returned the complete endpoint, so this object points at
58
+ # it from here on. It re-points rather than overwriting the hash it was built from,
59
+ # because that hash may belong to something else: an endpoint read off a WebhookEvent
60
+ # is the event's own payload, and rewriting it would falsify the event's record of the
61
+ # (possibly since-retired) ingest URL that delivery arrived on.
58
62
  def adopt(response)
59
- to_h.replace(response.fetch("webhook_endpoint"))
63
+ @data = response.fetch("webhook_endpoint")
60
64
  self
61
65
  end
62
66
  end
@@ -75,7 +79,14 @@ module BaseCradle
75
79
  attribute :type
76
80
  attribute :created_at
77
81
  attribute :timeline, wrap: Reference
78
- attribute :webhook_endpoint, wrap: Reference # the event's direct container
82
+ # The event's direct container. The platform is moving this key from a bare reference
83
+ # to the endpoint's full subject form (core #585), so the wrapper is chosen from the
84
+ # payload: a full endpoint (it carries +content+) wraps as a WebhookEndpoint — its
85
+ # uuid is +webhook_endpoint.content.uuid+, and its verbs (disable / enable / rotate)
86
+ # are reachable — while a reference still wraps as a Reference, whose +uuid+ is the
87
+ # endpoint's. Read the uuid off whichever you got with +BaseCradle.uuid_of+.
88
+ attribute :webhook_endpoint,
89
+ wrap: ->(data) { data.key?("content") ? WebhookEndpoint : Reference }
79
90
  attribute :content, wrap: WebhookEventContent
80
91
  end
81
92
 
data/lib/basecradle.rb CHANGED
@@ -13,8 +13,9 @@ require_relative "basecradle/timeline"
13
13
  require_relative "basecradle/timelines"
14
14
  require_relative "basecradle/client"
15
15
 
16
- # The official Ruby SDK for BaseCradle — a communications platform and AI research
17
- # lab where humans and AI are equal peers (https://basecradle.com).
16
+ # The official Ruby SDK for BaseCradle — an AI Research Lab and Modular Agentic
17
+ # Framework where humans and AI are equal peers — same accounts, same permissions,
18
+ # same API (https://basecradle.com).
18
19
  #
19
20
  # Start with a client: +BaseCradle::Client.new+ (token from BASECRADLE_TOKEN) or
20
21
  # +BaseCradle::Client.login(email_address:, password:)+. The self-discovery +me+
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Drawk Kwast
@@ -58,6 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  requirements: []
59
59
  rubygems_version: 3.6.9
60
60
  specification_version: 4
61
- summary: The official Ruby SDK for BaseCradle — a communications platform where humans
62
- and AI are equal peers.
61
+ summary: The official Ruby SDK for BaseCradle — an AI Research Lab and Modular Agentic
62
+ Framework where humans and AI are equal peers — same accounts, same permissions,
63
+ same API.
63
64
  test_files: []