basecradle 0.13.0 → 0.14.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: 6890e967dabc9e436aa26c23176989252913f567c1786b1f5ec3a57f9db825a1
4
- data.tar.gz: 19db33d569829502555ea735d9a9c4eb59f7997cfefb74db7995c39c627e89f6
3
+ metadata.gz: e52bd37f68c6961351dde4657cb665f346548f20023d9372166333ff943529b0
4
+ data.tar.gz: f5725693ace3374348be8ef9771db763533d7526ff902ac7f029b3425e061137
5
5
  SHA512:
6
- metadata.gz: b465e8f38d39746781b876d2bec7d4bc58eea8a93b39ede46f7b3553e52ea98402aa5de85a4274eef151a368289b06f5ddebd0cfbf8600f9bebfcd2320cf892b
7
- data.tar.gz: 7c612778aa574a28d4ad4b52c1e40c1e37fc2b8edab358e94ad6f07233af6726307d3ca65ec7e94efb39133f1e26fc42e415476d4d409313dccaa79e856da425
6
+ metadata.gz: 0c2c1cb2a1b5d23214bb743a6be23f1756565696bb2cb807f7c1634fa0fc4c24a326ea862fcbac2d25ed78b8f41f3ee1c1017dea3ca025909345a78b81703058
7
+ data.tar.gz: 70618de428d28dfa790c95f278ddcd043062c0759efd30f1c277204f1704b031977560ada0c8e31ad35c5ac8957e9010c4be846696b2ab011b6fc4a1eae0c91c
data/CHANGELOG.md CHANGED
@@ -9,6 +9,49 @@ section**. The newest heading is always the version `lib/basecradle/version.rb`
9
9
  a release writes its entry and its version in the same PR — and `test/changelog_test.rb`
10
10
  fails CI if the two ever disagree.
11
11
 
12
+ ## [0.14.0] - 2026-10-23
13
+
14
+ A parity release. The first full SDK parity audit (basecradle#715) checked every issue
15
+ ever filed on either SDK against the other's shipped code. This release brings over what
16
+ the Python SDK had and this one did not, plus one behavior the founder ruled on.
17
+
18
+ ### Added
19
+
20
+ - **The Dashboard's orientation blocks are typed**
21
+ ([#254](https://github.com/basecradle/basecradle-ruby/pull/254), mirroring
22
+ basecradle-python#188). `bc.me.environment.concepts_url` links the platform's
23
+ vocabulary. `bc.me.interaction.pagination` (a `BaseCradle::DashboardPagination`:
24
+ `summary`, `guide_url`) says how lists page and what is safe to checkpoint on.
25
+ `bc.me.interaction.tools` (a `BaseCradle::DashboardTools`: `summary`, `mapping_url`)
26
+ says that platform tools are thin wrappers over this same HTTP API. Both blocks read as
27
+ attributes, like every other section. They were previously reachable only as bare
28
+ Hashes through `[]`.
29
+ - **The drift-guard checks error codes too**
30
+ ([#253](https://github.com/basecradle/basecradle-ruby/pull/253), mirroring
31
+ basecradle-python#265). Besides the spec's endpoints, `rake test:live` now reads the
32
+ API docs' Error Codes table and fails when the platform documents a code that has no
33
+ typed exception in `BaseCradle::CODE_TO_ERROR`. Without a typed exception, callers would
34
+ get a bare `BaseCradle::Error` for that code. The offline suite also pins its error
35
+ catalog to the registry, so a mapping removed, or added without a test, fails CI.
36
+
37
+ ### Changed
38
+
39
+ - **`contact_message.add_note` appends the new note to `notes`**
40
+ ([#257](https://github.com/basecradle/basecradle-ruby/pull/257); founder ruling,
41
+ basecradle#715). It still returns the `BaseCradle::Note`. It now also appends that note
42
+ to the record's own `notes`, unless a note with the same `uuid` is already there, so you
43
+ no longer need to re-fetch the record to read it. This matches the Python SDK. Code that
44
+ appended the returned note by hand should stop doing so, or it will hold the note twice.
45
+ - **Retry backoff is capped at 8 seconds**
46
+ ([#255](https://github.com/basecradle/basecradle-ruby/pull/255), mirroring
47
+ basecradle-python#107). With `max_retries:` set, the wait still doubles from 0.5s, but it
48
+ never exceeds `BaseCradle::Client::RETRY_MAX_DELAY` (8.0). Before this, the tenth retry
49
+ waited 256 seconds.
50
+ - **Event Delivery is described as "outbound delivery", never "push"**
51
+ ([#256](https://github.com/basecradle/basecradle-ruby/pull/256), mirroring
52
+ basecradle-python#181). This is a wording change in the README and in the 0.4.0 entry
53
+ below; no behavior changes.
54
+
12
55
  ## [0.13.0] - 2026-10-23
13
56
 
14
57
  ### Added
@@ -593,7 +636,7 @@ enrichments come with them — but three things that worked on a `Hash` no longe
593
636
  header. The platform stores **at most one record per key** (scoped per timeline + author;
594
637
  per timeline for authorless webhook endpoints), so a replayed keyed create returns the
595
638
  **original record** — no duplicate record, no second **Event Delivery** event, no task
596
- activation. (Event Delivery is the platform's *outbound* push through your integration;
639
+ activation. (Event Delivery is the platform's *outbound* delivery to your integration;
597
640
  the webhook endpoints named above are the *inbound* feature the SDK models — opposite
598
641
  directions, different features.) A key identifies one logical create: the same key with a
599
642
  different body still returns the original record. Keys never expire and never appear in a
@@ -687,6 +730,7 @@ the Python SDK's behavior in idiomatic Ruby. Zero runtime dependencies.
687
730
  - **Quality bars** — a README-as-tested-doc harness (every example runs against a mocked
688
731
  API) and a spec drift-guard (CI fails if the live API grows beyond the SDK).
689
732
 
733
+ [0.14.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.14.0
690
734
  [0.13.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.13.0
691
735
  [0.12.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.12.0
692
736
  [0.11.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.11.0
data/README.md CHANGED
@@ -175,7 +175,7 @@ inventing a value.
175
175
 
176
176
  ## Webhooks
177
177
 
178
- 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.
178
+ 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 counterpart is Event Delivery, the platform's outbound delivery to your integration, which the SDK does not model.
179
179
 
180
180
  ```ruby
181
181
  require "basecradle"
@@ -258,7 +258,7 @@ A create can succeed on the server while its response is lost on the wire — re
258
258
 
259
259
  Keys are scoped **per timeline and per author** for all four resources — yours never collide with another peer's, and the same key on two timelines creates two records.
260
260
 
261
- Opt into automatic retries with `max_retries:`. It is off by default, and even when on it only re-sends what's safe: any read (`GET`) and any create that carries an `idempotency_key`. An **unkeyed** create is never retried — which is why the two features ship together.
261
+ Opt into automatic retries with `max_retries:`. It is off by default, and even when on it only re-sends what's safe: any read (`GET`) and any create that carries an `idempotency_key`. An **unkeyed** create is never retried — which is why the two features ship together. Retries back off exponentially — 0.5s, 1s, 2s, … — capped at 8s between attempts.
262
262
 
263
263
  ```ruby
264
264
  require "basecradle"
@@ -387,8 +387,8 @@ bc.notes.each { |n| puts n.body } # every note, across every subject, newest fi
387
387
  A contact message is a flat record with no `type`/`content` wrapper, so `message.uuid`
388
388
  is its identity. `set_status` changes the triage verdict in any direction (reopen with
389
389
  `"received"`). It updates the object you called it on and returns it. `add_note` returns
390
- the new `BaseCradle::Note` and leaves `message.notes` as it was fetched, so call
391
- `bc.contact_messages.get` again to read the record with the new note embedded. A note
390
+ the new `BaseCradle::Note` and appends it to `message.notes`, unless a note with that
391
+ `uuid` is already there. A note can never be edited or deleted afterwards. A note
392
392
  body is not idempotent, so `add_note` is never retried automatically.
393
393
 
394
394
  `data` is a plain `Hash`, exactly as the platform sent it. The SDK does not model the
@@ -578,7 +578,7 @@ deliberately.
578
578
  ```bash
579
579
  bundle install # install dev dependencies
580
580
  bundle exec rake # lint + tests (offline — the default)
581
- bundle exec rake test:live # the spec drift-guard (one network call to the live spec)
581
+ bundle exec rake test:live # the drift-guard (one GET each of the live spec and API docs)
582
582
  bundle exec rubocop # lint only
583
583
  gem build basecradle.gemspec # build the gem
584
584
  actionlint # lint the GitHub workflows (CI runs this; see below)
@@ -40,8 +40,11 @@ module BaseCradle
40
40
  # idempotency key (see +max_retries+).
41
41
  DEFAULT_MAX_RETRIES = 0
42
42
 
43
- # Backoff between retries doubles each attempt from this base (0.5s, 1s, 2s, …).
43
+ # Backoff between retries doubles each attempt from this base (0.5s, 1s, 2s, …), and
44
+ # is capped at RETRY_MAX_DELAY seconds, so a generous max_retries never sleeps for
45
+ # minutes between attempts.
44
46
  RETRY_BASE_DELAY = 0.5
47
+ RETRY_MAX_DELAY = 8.0
45
48
 
46
49
  # Connection failures Net::HTTP raises that mean "the request never got a response".
47
50
  CONNECTION_ERRORS = [
@@ -73,7 +76,8 @@ module BaseCradle
73
76
  # off. When set above 0, only requests that are safe to re-send are retried: any +GET+
74
77
  # (reads change nothing) and any create carrying an +idempotency_key+ (the platform
75
78
  # dedupes keyed creates, so a resend can't duplicate the record). An unkeyed +POST+ is
76
- # never retried, whatever this is set to. Retries back off exponentially.
79
+ # never retried, whatever this is set to. Retries back off exponentially, capped at
80
+ # RETRY_MAX_DELAY seconds.
77
81
  def initialize(token = nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT,
78
82
  max_retries: DEFAULT_MAX_RETRIES)
79
83
  resolved = token || ENV.fetch("BASECRADLE_TOKEN", nil)
@@ -347,9 +351,9 @@ module BaseCradle
347
351
  (200..299).cover?(response.code.to_i)
348
352
  end
349
353
 
350
- # Exponential backoff before the next attempt (attempt is 1-based).
354
+ # Exponential backoff before the next attempt (attempt is 1-based), capped.
351
355
  def backoff(attempt)
352
- sleep(RETRY_BASE_DELAY * (2**(attempt - 1)))
356
+ sleep([ RETRY_BASE_DELAY * 2**(attempt - 1), RETRY_MAX_DELAY ].min)
353
357
  end
354
358
 
355
359
  def build_uri(path, params)
@@ -85,18 +85,23 @@ module BaseCradle
85
85
  end
86
86
 
87
87
  # Write a note about this contact message, and return it as a +Note+ (you are its
88
- # author). Raises +ValidationError+ (422) for a blank body.
88
+ # author). A note can never be edited or deleted afterwards, by anyone. Raises
89
+ # +ValidationError+ (422) for a blank body, and appends nothing.
89
90
  #
90
- # This object's own +notes+ is not touched: it is the record as fetched, and the API
91
- # answers with the note alone. Fetch the contact message again
92
- # (+bc.contact_messages.get(uuid)+) to read it with the new note embedded.
91
+ # The API answers with the note alone, in full, and this object appends it to its own
92
+ # +notes+ — unless a note with that +uuid+ is already there.
93
93
  #
94
94
  # Never auto-retried — the notes endpoint takes no +Idempotency-Key+, so a resend after
95
95
  # a lost response could write the note twice.
96
96
  def add_note(body:)
97
97
  response = require_client.request("POST", "/contact_messages/#{uuid}/notes",
98
98
  json: { "note" => { "body" => body } })
99
- Note.new(response.fetch("note"), client: @client)
99
+ note = response.fetch("note")
100
+ notes = @data["notes"] || []
101
+ unless notes.any? { |existing| existing["uuid"] == note["uuid"] }
102
+ @data = @data.merge("notes" => notes + [ note ])
103
+ end
104
+ Note.new(note, client: @client)
100
105
  end
101
106
  end
102
107
 
@@ -15,6 +15,19 @@ module BaseCradle
15
15
  attribute :name
16
16
  attribute :summary
17
17
  attribute :you_are
18
+ attribute :concepts_url # the vocabulary: timeline, participation, trust, task, ...
19
+ end
20
+
21
+ # How lists page, and what is safe to checkpoint on — the gist plus the full guide.
22
+ class DashboardPagination < ApiObject
23
+ attribute :summary
24
+ attribute :guide_url
25
+ end
26
+
27
+ # Platform tools are thin wrappers over this same HTTP API — there is no other backend.
28
+ class DashboardTools < ApiObject
29
+ attribute :summary
30
+ attribute :mapping_url # the tool-action → endpoint table
18
31
  end
19
32
 
20
33
  # Your data surfaces — timelines first, then every cross-timeline list.
@@ -25,6 +38,8 @@ module BaseCradle
25
38
  attribute :tasks_url
26
39
  attribute :webhook_endpoints_url
27
40
  attribute :webhook_events_url
41
+ attribute :pagination, wrap: DashboardPagination
42
+ attribute :tools, wrap: DashboardTools
28
43
  end
29
44
 
30
45
  # Where to manage yourself: profile, sessions, password.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BaseCradle
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.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.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Drawk Kwast