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 +4 -4
- data/CHANGELOG.md +82 -10
- data/README.md +18 -4
- data/lib/basecradle/api_object.rb +12 -4
- data/lib/basecradle/client.rb +15 -0
- data/lib/basecradle/errors.rb +15 -0
- data/lib/basecradle/items.rb +20 -2
- data/lib/basecradle/timeline.rb +13 -4
- data/lib/basecradle/version.rb +1 -1
- data/lib/basecradle/webhooks.rb +14 -3
- data/lib/basecradle.rb +3 -2
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdd852de1690fbb03f684277c2608371ed8828eb7850d4df6a00cf4de2a8cd09
|
|
4
|
+
data.tar.gz: 80e7e89292022879aafccaa7b9676329c27c9c7692e83ed5e2a78540dbb4b082
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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`**
|
|
63
|
-
who was a viewer at deletion, with a `resource` pointer that
|
|
64
|
-
alongside `timeline.delete`. The SDK exposes no
|
|
65
|
-
there is no new type or constant; the semantics are captured
|
|
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
|
|
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) —
|
|
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`
|
|
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).
|
|
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,
|
|
90
|
+
def wrap_value(value, wrap)
|
|
89
91
|
case value
|
|
90
92
|
when Hash
|
|
91
|
-
|
|
93
|
+
wrap_one(value, wrap)
|
|
92
94
|
when Array
|
|
93
|
-
value.map { |item| item.is_a?(Hash) ?
|
|
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 " \
|
data/lib/basecradle/client.rb
CHANGED
|
@@ -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
|
data/lib/basecradle/errors.rb
CHANGED
|
@@ -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,
|
data/lib/basecradle/items.rb
CHANGED
|
@@ -56,12 +56,29 @@ module BaseCradle
|
|
|
56
56
|
attribute :uuid
|
|
57
57
|
attribute :instructions
|
|
58
58
|
attribute :activate_at
|
|
59
|
-
|
|
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" | "
|
|
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?
|
data/lib/basecradle/timeline.rb
CHANGED
|
@@ -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
|
-
|
|
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+
|
|
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
|
-
|
|
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 <<
|
|
74
|
+
roster << data unless roster.any? { |p| p["uuid"] == added.uuid }
|
|
66
75
|
added
|
|
67
76
|
end
|
|
68
77
|
|
data/lib/basecradle/version.rb
CHANGED
data/lib/basecradle/webhooks.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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 —
|
|
17
|
-
#
|
|
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.
|
|
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 —
|
|
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: []
|