Skip to content

Merge tag-sync-PoC into main — align main with production gem#27

Merged
ahorovit merged 11 commits into
mainfrom
adin/tag-sync-PoC
Jun 9, 2026
Merged

Merge tag-sync-PoC into main — align main with production gem#27
ahorovit merged 11 commits into
mainfrom
adin/tag-sync-PoC

Conversation

@ahorovit

@ahorovit ahorovit commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Bring main up to date with the eleven commits that have been accumulating on adin/tag-sync-PoC since December 2024 — code that has already been running in production via Speckel's Gemfile pin. Post-merge, main reflects the gem we actually ship.

This is housekeeping, not a feature PR. The merge is a fast-forward and a no-op for production behavior.

Problem

Our main is frozen at June 2024 (a CI commit by Petro Podrezo). All real gem work since December 2024 has landed on adin/tag-sync-PoC via PR #25 (BAN-8680) and PR #26 (BAN-8729) — both targeted the PoC branch directly rather than main. The original attempt to merge PoC → main, PR #24, was opened 2024-12-18 and closed unmerged on 2025-02-27 without comments or stated reason.

Meanwhile, Speckel's Gemfile has been pinning to branch: 'adin/tag-sync-PoC' the entire time. Production has been running the "PoC" branch for over a year.

Two consequences worth fixing:

  • main does not reflect the gem code we ship. Anyone reading lib/ on main would conclude the gem is missing DeveloperClient, bulk tag operations, and the v4 tag-update endpoint.
  • There's no clean base for follow-up work. Imminent next step: fix an RFC 7009 violation in revoke_token — the gem POSTs Content-Type: application/json to Kit's /oauth/revoke, which strictly requires application/x-www-form-urlencoded. That fix needs to branch off a main that's actually current.

There is no upstream to sync with — ConvertKit/convertkit-api-ruby was deleted after Kit's rebrand. This repo is the only convertkit-api-ruby left on GitHub. We are the maintainers by default.

Solution

Fast-forward main to adin/tag-sync-PoC (commit 862a9426). The adin/tag-sync-PoC branch is retained, not deleted on merge — Speckel's Gemfile still names it, so the branch must outlive this PR until a follow-up in Speckel repoints the gem source at main (or at a tag cut from it).

What ships, grouped by what the reviewer needs to evaluate:

DeveloperClient — API-key authentication

lib/convertkit/client.rb — new ConvertKit::DeveloperClient < Client that authenticates with an API key instead of an OAuth access token. Some Kit endpoints can only be reached with a developer API key.

lib/convertkit/connection.rb — the constructor now builds headers via a default_headers(options) helper. When options[:api_key] is present, the connection adds X-Kit-Api-Key: <key> to every request. Bearer-token auth (options[:auth_token]) is unchanged in behavior; the only structural change is moving header construction into a private method to keep both auth modes side-by-side.

def default_headers(options)
  headers = { 'Content-Type' => MIME_TYPE }
  headers['X-Kit-Api-Key'] = options[:api_key] if options[:api_key]
  headers
end

New spec at spec/lib/convertkit/developer_client_spec.rb.

Tags resource — update method and create bugfix

lib/convertkit/resources/tags.rb:

  • New Tags#update(tag_id, options = {}) — PUT /tags/:id, per the v4 update-tag-name endpoint. Currently accepts name:.
  • Bugfix Tags#create — previously returned TagResponse.new(response), which wrapped the entire JSON envelope instead of the inner tag object. Now returns TagResponse.new(response['tag']), matching what every other Tags method does. Spec updated to assert the new shape.

Bulk tag operations

lib/convertkit/resources/tags.rb adds two methods:

  • Tags#bulk_add_to_subscribers(taggings) — POST /bulk/tags/subscribers, returns SubscriberBulkAddTagResponse.
  • Tags#bulk_remove_from_subscribers(taggings) — DELETE /bulk/tags/subscribers, returns SubscriberBulkRemoveTagResponse.

Both raise ArgumentError when taggings isn't an Array. Backed by three new response classes plus one new subscriber shape:

New class Purpose
SubscriberBulkAddTagResponse Wraps subscribers: (array of TaggedSubscriberResponse) and failures: (array of SubscriberBulkTagFailureResponse)
SubscriberBulkRemoveTagResponse Wraps failures: only — successful removes return no per-subscriber payload
SubscriberBulkTagFailureResponse Exposes .tagging (SubscriberTagResponse) and .errors so callers can identify which (subscriber_id, tag_id) pair failed and why
TaggedSubscriberResponse (in subscriber_response.rb) Subscriber + the tagged_at timestamp returned by bulk-add
SubscriberTagResponse The (subscriber_id, tag_id) pair attached to each failure

lib/convertkit.rb requires all five new files so ConvertKit::Resources::* resolves them.

Registry

lib/convertkit.rb — five new require lines, alphabetically interleaved with existing entries. No load-order subtleties.

Design Decisions

Decision Context
Fast-forward merge (no squash) Preserves authorship and the merge commits from PRs #25 and #26 that already document their work. Squashing would discard the per-commit ticket references for code that has been in production for months.
Retain adin/tag-sync-PoC after merge Speckel's Gemfile still names this branch. Deleting it as part of this merge would break Speckel's bundle install. The branch will be removed in a follow-up after Speckel's Gemfile is repointed at main (or a tag cut from it).

Blast Radius

Zero behavioral risk at merge time. Speckel already pins to SHA 862a9426d230, which is the tip of adin/tag-sync-PoC and the same commit that becomes the new tip of main. The fast-forward merge produces no new code; it just makes main reachable to that SHA.

The code being aligned with main is in active production use by Speckel — DeveloperClient, bulk tag ops, and the v4 Tags#update method have all been exercised in convert_kit_service.rb and sync_convert_kit_subscriber.rb jobs for months.

Monitor nothing — there's nothing new shipping. The next PR (the /revoke Content-Type fix) is where production-monitoring matters.

Rollback Plan

Revert via GitHub's Revert button on this PR. Since adin/tag-sync-PoC is retained, Speckel continues to pin against the unchanged branch tip — reverting main does not perturb production.

Dependencies

None blocking this merge. Follow-up work that depends on this PR landing:

  1. Cut a stable tag (e.g. v0.0.13) from main after merge.
  2. Open a follow-up PR in this repo: fix ConvertKit::OAuth#revoke_token to send form-encoded params per RFC 7009 §2.1. Branched off main.
  3. Open a follow-up PR in Speckel that repoints the Gemfile from branch: 'adin/tag-sync-PoC' to the new tag (or to main).
  4. After step 3 deploys, delete adin/tag-sync-PoC.

Test plan

Verifiable by agent before merging

  • Confirmed the PoC branch is a true fast-forward of maingit log main..adin/tag-sync-PoC shows 11 commits; reverse direction is empty.
  • Confirmed Speckel's Gemfile.lock pins the gem at SHA 862a9426d230, which is the current tip of adin/tag-sync-PoC and the new tip of main post-merge.
  • Verified no upstream ConvertKit/convertkit-api-ruby repo exists to sync from (HTTP 404).
  • Verified no open PRs currently target main in this repo.

Cannot be verified before merge

  • Confirm Speckel CI still resolves the gem after merge — Speckel's bundler resolves against branch HEAD, so this remains stable as long as we don't delete the branch. Requires a Speckel CI run to observe.

@ahorovit ahorovit self-assigned this Jun 8, 2026
@ahorovit ahorovit merged commit 68a60f7 into main Jun 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants