traject-solr_pool 0.1.0 → 0.1.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: 5ba919dd1768bd1f05ff1e29af8236790aee47bd0333f97c9be2cd66b691fa0a
4
- data.tar.gz: 01e411c06eb9dd7f101630e42829c0dddacb13931ee1bb0c38f4966383fdcb5a
3
+ metadata.gz: cfec86e670097a69b2497eec78b34d942a3607c2235a35f5141ae7e5c12709dd
4
+ data.tar.gz: c67b4176576d49edb174c18c52dbdc8c0a1ae3cf3e6c45d8e32270a59d961dca
5
5
  SHA512:
6
- metadata.gz: 131acdc7092f6f935ba9d16b1aa4c50047b86694d06dc4ba571c4d8415624ff461efe549351e4c8db76d106ab58539f469fd3491bc8bd709d1875d65eaa73794
7
- data.tar.gz: '0687c890c8944c6b2b024c49b09748f78c8626a7a8531ce0aac553cc5c5bd173537e7ef9cf0a30f23c33976022e7a41f40d2587cb4056bfe5fb23a1e0f8cd040'
6
+ metadata.gz: 7a1a91726739555f50720e8290346b2d4563294de41fd5d59093cef7375db54e68c7a50dd5ef2c9d236da26162293dae5321fb8e4b26abd0269d530c2f8297b3
7
+ data.tar.gz: 9ae93346c8a65de4c826f62ccc872fea715cefdc22f85d121f253008dc1b72d71ba5d1c6cdc629dd157974354f0f08aa502491102c3a43f487ca1353fa05ebd3
@@ -0,0 +1,57 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*.*.*']
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: write # create the GitHub Release and attach assets
12
+ id-token: write # OIDC token for RubyGems Trusted Publishing
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: '3.4'
18
+ bundler-cache: true
19
+
20
+ - name: Verify tag matches gem version
21
+ run: |
22
+ gem_version="$(ruby -Ilib -r traject/solr_pool/version \
23
+ -e 'print Traject::SolrPool::VERSION')"
24
+ tag_version="${GITHUB_REF_NAME#v}"
25
+ if [ "$gem_version" != "$tag_version" ]; then
26
+ echo "Tag $GITHUB_REF_NAME does not match VERSION $gem_version" >&2
27
+ exit 1
28
+ fi
29
+
30
+ # Re-run the specs (not RuboCop) before publishing. A tag push does not
31
+ # trigger ci.yml, and dependencies resolve fresh (Gemfile.lock is
32
+ # gitignored), so this guards against a broken artifact under a newer
33
+ # in-range dependency the last main CI run never saw. A yanked version
34
+ # number can never be reused, so publishing red is uniquely costly.
35
+ - run: bundle exec rake spec
36
+
37
+ # Builds via `rake release` and pushes over OIDC. It leaves the exact
38
+ # published .gem in pkg/, which the steps below checksum and attach.
39
+ - uses: rubygems/release-gem@v1
40
+
41
+ # Record digests of the artifact that was actually published (pkg/*.gem
42
+ # from the step above). .gem builds are not byte-stable across
43
+ # zlib/RubyGems versions, so the published gem is the source of truth
44
+ # rather than a repo-committed checksum compared against a fresh build.
45
+ - name: Checksum the published gem
46
+ run: bundle exec rake build:checksum
47
+
48
+ - name: Create GitHub Release with gem and checksums
49
+ env:
50
+ GH_TOKEN: ${{ github.token }}
51
+ run: |
52
+ gh release create "$GITHUB_REF_NAME" \
53
+ --title "$GITHUB_REF_NAME" \
54
+ --generate-notes \
55
+ pkg/traject-solr_pool-*.gem \
56
+ checksums/traject-solr_pool-*.sha256 \
57
+ checksums/traject-solr_pool-*.sha512
data/CLAUDE.md CHANGED
@@ -207,6 +207,10 @@ matching the file tree.
207
207
  | `rake spec` | RSpec only |
208
208
  | `rake rubocop` | RuboCop only |
209
209
  | `rake bundle:audit:check` | Offline CVE scan |
210
+ | `rake build:checksum` | Build, then write SHA-256 + SHA-512 to `checksums/` (gitignored) |
211
+ | `rake bump:patch` | Bump patch version in version.rb, print release commands |
212
+ | `rake bump:minor` | Bump minor version in version.rb, print release commands |
213
+ | `rake bump:major` | Bump major version in version.rb, print release commands |
210
214
 
211
215
  `rake ci` must run clean before any commit. Never bypass bundler-audit.
212
216
 
@@ -218,8 +222,13 @@ RSpec), plus a separate `security` job running `rake audit` (network advisory-DB
218
222
  refresh + check). MRI only — mirrors `http_connection_pool`'s CI. `Gemfile.lock`
219
223
  is gitignored, so CI resolves dependencies fresh each run.
220
224
 
221
- Automated publishing (`release.yml` / OIDC Trusted Publishing) is planned but
222
- not yet wired; the initial release is published manually by the maintainer.
225
+ `.github/workflows/release.yml` triggers on a `v*.*.*` tag: it verifies the tag
226
+ matches `Traject::SolrPool::VERSION`, re-runs the specs, publishes via
227
+ `rubygems/release-gem` (OIDC Trusted Publishing — no stored API key), then
228
+ checksums the published gem and creates a GitHub Release with the gem and
229
+ checksum files attached. `gem push` / `git push` / pushing a tag remain
230
+ user-only actions; `rake bump:*` only edits `version.rb` and prints the next
231
+ commands.
223
232
 
224
233
  ---
225
234
 
data/README.md CHANGED
@@ -68,6 +68,21 @@ auth credentials are sent as an `Authorization` header, never baked into the
68
68
  origin, so distinct credentials resolve to distinct pools and never share
69
69
  connections.
70
70
 
71
+ ### Health check
72
+
73
+ ```ruby
74
+ writer = Traject::SolrPool::SolrJsonWriter.new(settings)
75
+ abort 'Solr unreachable' unless writer.ready?
76
+ ```
77
+
78
+ `ready?` returns `true` for any HTTP response (the server answered — including
79
+ `405`/`404`), and `false` only on a transport failure (refused/timeout/DNS). It
80
+ uses a HEAD request so the pooled persistent connection stays clean for reuse.
81
+ Use `writer.ping` for the raw response. Configure the endpoint with
82
+ `solr_writer.ping_path` (default `<core>/admin/ping`) and the timeout with
83
+ `solr_writer.ping_timeout` (default 5s). See [docs/usage.md](docs/usage.md) for
84
+ the full reference, including reusing the pool from another class.
85
+
71
86
  ## Settings
72
87
 
73
88
  The writer honours the stock `solr.*` / `solr_writer.*` vocabulary, plus one
@@ -160,6 +175,22 @@ also available.
160
175
  Bug reports and pull requests are welcome. Releasing and pushing are maintainer
161
176
  responsibilities — contributors should not push tags or publish gems.
162
177
 
178
+ ## Releasing
179
+
180
+ Releases are published to RubyGems via OIDC Trusted Publishing on a version
181
+ tag — no API key is stored. The maintainer:
182
+
183
+ 1. Bumps the version: `rake bump:patch` (or `:minor` / `:major`). This edits
184
+ `lib/traject/solr_pool/version.rb` and prints the next commands.
185
+ 2. Commits and tags: `git commit -am 'Release vX.Y.Z' && git tag vX.Y.Z`.
186
+ 3. Pushes the tag: `git push && git push --tags`.
187
+
188
+ Pushing the `v*.*.*` tag triggers `.github/workflows/release.yml`, which
189
+ verifies the tag matches `Traject::SolrPool::VERSION`, re-runs the specs,
190
+ publishes the gem, and creates a GitHub Release with the gem and its SHA-256 /
191
+ SHA-512 checksums attached. `gem push` and `git push` are maintainer actions;
192
+ CI never pushes on its own.
193
+
163
194
  ## License
164
195
 
165
196
  The gem is available as open source under the terms of the [MIT License][mit].
data/Rakefile CHANGED
@@ -4,6 +4,7 @@ require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
  require 'rubocop/rake_task'
6
6
  require 'bundler/audit/task'
7
+ require_relative 'tasks/version_bumper'
7
8
 
8
9
  RSpec::Core::RakeTask.new(:spec)
9
10
  RuboCop::RakeTask.new
@@ -19,3 +20,69 @@ desc 'Audit dependencies (offline), run RuboCop, then the RSpec suite'
19
20
  task ci: %i[bundle:audit:check rubocop spec]
20
21
 
21
22
  task default: :ci
23
+
24
+ VERSION_FILE = 'lib/traject/solr_pool/version.rb'
25
+
26
+ namespace :bump do
27
+ VersionBumper::LEVELS.each do |level|
28
+ desc "Bump the #{level} version and print the release commands"
29
+ task(level) { bump_version(level) }
30
+ end
31
+ end
32
+
33
+ def bump_version(level)
34
+ contents = File.read(VERSION_FILE)
35
+ current = contents[/VERSION = '([^']+)'/, 1]
36
+ raise "could not find VERSION in #{VERSION_FILE}" unless current
37
+
38
+ bumped = VersionBumper.next(current, level)
39
+ File.write(VERSION_FILE, contents.sub(/VERSION = '[^']+'/, "VERSION = '#{bumped}'"))
40
+ puts "Bumped #{current} -> #{bumped}"
41
+ print_release_steps(bumped)
42
+ end
43
+
44
+ def print_release_steps(bumped)
45
+ puts <<~NEXT
46
+ Next steps (run these yourself):
47
+ git add #{VERSION_FILE}
48
+ git commit -m 'Release v#{bumped}'
49
+ git tag v#{bumped}
50
+ git push && git push --tags
51
+ NEXT
52
+ end
53
+
54
+ # `bundler/gem_tasks` provides `build` (into pkg/). We extend it to record
55
+ # SHA-256 and SHA-512 digests of the built .gem in the standard
56
+ # `sha256sum -c` / `sha512sum -c` format. These describe THIS build's artifact;
57
+ # the release workflow runs this against the exact gem it just published and
58
+ # attaches the digests to the GitHub Release. .gem builds are not byte-stable
59
+ # across zlib/RubyGems versions, so checksums are recorded per published
60
+ # artifact rather than committed to the repo and byte-compared in CI.
61
+ namespace :build do
62
+ desc 'Build the gem, then write SHA-256 and SHA-512 checksums to checksums/'
63
+ task :checksum do
64
+ require 'digest'
65
+ require_relative 'lib/traject/solr_pool/version'
66
+
67
+ gem_path = File.join('pkg', "traject-solr_pool-#{Traject::SolrPool::VERSION}.gem")
68
+ Rake::Task['build'].invoke unless File.exist?(gem_path)
69
+
70
+ write_checksum(gem_path, Digest::SHA256, 'sha256')
71
+ write_checksum(gem_path, Digest::SHA512, 'sha512')
72
+
73
+ # bundler/gem_tasks' build writes its own raw-format `<name>.gem.sha512`
74
+ # (digest only, no filename). Remove it so checksums/ holds exactly one
75
+ # sha256 and one sha512, both in the standard `<digest> <file>` format.
76
+ redundant = File.join('checksums', "#{File.basename(gem_path)}.sha512")
77
+ rm_f redundant
78
+ end
79
+ end
80
+
81
+ def write_checksum(gem_path, digest_class, extension)
82
+ mkdir_p 'checksums'
83
+ digest = digest_class.file(gem_path).hexdigest
84
+ basename = File.basename(gem_path, '.gem')
85
+ out = File.join('checksums', "#{basename}.#{extension}")
86
+ File.write(out, "#{digest} #{File.basename(gem_path)}\n")
87
+ puts "#{extension}: #{digest} -> #{out}"
88
+ end
@@ -18,6 +18,17 @@ module Traject
18
18
  end
19
19
  end
20
20
 
21
+ # Transport-level failures that mean the server is unreachable. A returned
22
+ # HTTP response of ANY status (2xx/4xx/5xx) is NOT in this set — it proves
23
+ # the server answered. Kept independent of the writer's skippable list.
24
+ TRANSPORT_ERRORS = [
25
+ HTTP::TimeoutError,
26
+ HttpConnectionPool::TimeoutError,
27
+ HTTP::ConnectionError,
28
+ SocketError,
29
+ Errno::ECONNREFUSED
30
+ ].freeze
31
+
21
32
  attr_reader :origin
22
33
 
23
34
  def initialize(origin:, pool_size:, pool_timeout: nil, **opts)
@@ -45,6 +56,28 @@ module Traject
45
56
  end
46
57
  end
47
58
 
59
+ def head(path, timeout: nil)
60
+ @adapter.with_connection do |conn|
61
+ client = timeout ? conn.timeout(timeout) : conn
62
+ client.head(path)
63
+ end
64
+ end
65
+
66
+ # Raw health probe: returns the HTTP::Response (any status) so callers can
67
+ # inspect it. Raises on transport failure. HEAD has no body to drain, so
68
+ # it leaves the pooled persistent socket clean for reuse.
69
+ def ping(path, timeout: nil)
70
+ head(path, timeout: timeout)
71
+ end
72
+
73
+ # Liveness predicate. Any HTTP response means reachable -> true. Only a
74
+ # transport failure means down -> false. Never raises.
75
+ def ready?(path, timeout: nil)
76
+ !ping(path, timeout: timeout).nil?
77
+ rescue *TRANSPORT_ERRORS
78
+ false
79
+ end
80
+
48
81
  def release
49
82
  @adapter.release_connection_pool
50
83
  end
@@ -140,6 +140,14 @@ module Traject
140
140
  delete(query: '*:*')
141
141
  end
142
142
 
143
+ def ping(timeout: nil)
144
+ connection.ping(@ping_path, timeout: timeout || @ping_timeout)
145
+ end
146
+
147
+ def ready?(timeout: nil)
148
+ connection.ready?(@ping_path, timeout: timeout || @ping_timeout)
149
+ end
150
+
143
151
  private
144
152
 
145
153
  def shutdown_thread_pool
@@ -207,6 +215,7 @@ module Traject
207
215
  configure_batching
208
216
  configure_pools
209
217
  configure_commit
218
+ configure_ping
210
219
  end
211
220
 
212
221
  def configure_skipped
@@ -240,6 +249,21 @@ module Traject
240
249
  @commit_timeout = (@settings['solr_writer.commit_timeout'] || 600).to_i
241
250
  end
242
251
 
252
+ def configure_ping
253
+ @ping_path = @settings['solr_writer.ping_path'] || derive_ping_path
254
+ @ping_timeout = (@settings['solr_writer.ping_timeout'] || 5).to_i
255
+ end
256
+
257
+ # Derive <core>/admin/ping from the update URL path by stripping a
258
+ # trailing /update or /update/json segment. Falls back to appending
259
+ # /admin/ping to the whole path when no update segment is present.
260
+ def derive_ping_path
261
+ path = URI.parse(@solr_update_url).path
262
+ base = path.sub(%r{/update(/json)?/?\z}, '')
263
+ base = path if base.empty?
264
+ "#{base}/admin/ping"
265
+ end
266
+
243
267
  # Splits origin (scheme://host:port) from request path; auth goes to
244
268
  # Connection as an Authorization header, never baked into the origin.
245
269
  def build_connection(user, password)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Traject
4
4
  module SolrPool
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: traject-solr_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - bbarberBPL
@@ -52,15 +52,11 @@ extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
54
  - ".github/workflows/ci.yml"
55
+ - ".github/workflows/release.yml"
55
56
  - CLAUDE.md
56
57
  - LICENSE.txt
57
58
  - README.md
58
59
  - Rakefile
59
- - docs/agents/README.md
60
- - docs/skills/README.md
61
- - docs/superpowers/plans/2026-07-01-traject-solr-pool-writer.md
62
- - docs/superpowers/specs/2026-07-01-traject-solr-pool-writer-design.md
63
- - docs/usage.md
64
60
  - lib/traject/solr_pool.rb
65
61
  - lib/traject/solr_pool/connection.rb
66
62
  - lib/traject/solr_pool/solr_json_writer.rb
@@ -88,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
84
  - !ruby/object:Gem::Version
89
85
  version: '0'
90
86
  requirements: []
91
- rubygems_version: 4.0.15
87
+ rubygems_version: 3.6.9
92
88
  specification_version: 4
93
89
  summary: Traject Solr JSON writer backed by a persistent, pooled, thread-safe HTTP
94
90
  client
@@ -1,41 +0,0 @@
1
- # Agents
2
-
3
- Project-specific subagent definitions for `traject-solr_pool`.
4
-
5
- When a task in this project benefits from a dedicated subagent (a focused
6
- reviewer, a migration helper, a release auditor, etc.), define it here as one
7
- Markdown file per agent. Keep each agent's responsibility narrow and its
8
- instructions self-contained, so it can run without the parent session's
9
- context.
10
-
11
- ## File convention
12
-
13
- - One file per agent: `docs/agents/<agent-name>.md`.
14
- - Start with a short purpose line: what the agent does and when to reach for it.
15
- - List the tools it needs and the inputs it expects (file paths, not pasted
16
- context).
17
- - Describe the report format it must return.
18
-
19
- ## Reusable from the sibling `http_connection_pool` gem
20
-
21
- This gem shares that gem's thread-safety and security constraints, so several of
22
- its agents transfer directly (see `../../../http_connection_pool/docs/agents/`).
23
- Adapt the file paths/tags to this project when reused:
24
-
25
- - `concurrency-spec-reviewer` — reviews concurrency specs for assertions a race
26
- can invalidate, then stress-loops them for flakiness. Directly applicable to
27
- our `:thread_safety` and `:background_jobs` integration specs.
28
- - `dependency-security-auditor` — audits the dependency tree against primary
29
- sources (RubyGems API, GitHub advisory DB). Applicable to our gemspec/Gemfile
30
- whenever a dependency floor changes.
31
- - `memory-leak-auditor` — hunts unbounded retention with `ObjectSpace`/`GC`
32
- probes. Applicable to verifying the writer/pool do not accumulate objects
33
- per-batch or per-job.
34
-
35
- ## Defined agents
36
-
37
- _None yet. Candidate for this project:_
38
-
39
- - **writer-parity-reviewer** — diff our `Traject::SolrPool::SolrJsonWriter`
40
- against the stock `Traject::SolrJsonWriter` to confirm settings/method-surface
41
- parity and flag any silently-dropped behaviour. Advisory only.
@@ -1,42 +0,0 @@
1
- # Skills
2
-
3
- Project-specific skills for `traject-solr_pool`.
4
-
5
- When a repeatable workflow emerges in this project (a release checklist, a
6
- dependency-audit routine, a Solr round-trip verification), capture it here as a
7
- skill so future sessions follow the same steps instead of rediscovering them.
8
-
9
- ## File convention
10
-
11
- - One directory per skill: `docs/skills/<skill-name>/SKILL.md`.
12
- - The `SKILL.md` opens with YAML frontmatter (`name`, `description`) describing
13
- when the skill applies, followed by the steps.
14
- - Keep steps concrete and ordered; prefer checklists the session can turn into
15
- todos.
16
- - Reference supporting files by relative path within the skill directory.
17
-
18
- ## Reusable from the sibling `http_connection_pool` gem
19
-
20
- See `../../../http_connection_pool/docs/skills/`. These transfer with minor path
21
- changes:
22
-
23
- - `dependency-audit` — security sweep that verifies every advisory/version claim
24
- against primary sources before recommending a change. Reuse verbatim whenever
25
- a dependency floor changes.
26
- - `memory-leak-audit` — drives churn and measures retention with
27
- `ObjectSpace`/`GC` rather than reading code. Adapt the churn driver to enqueue
28
- writer batches / background jobs against a WebMock-stubbed Solr.
29
-
30
- ## Defined skills
31
-
32
- _None yet. Candidates for this project:_
33
-
34
- - **edge-traject-cutover** — _done (2026-07-27)._ traject 3.9.0 lifted the
35
- `http < 6` cap; the gemspec now pins `traject '~> 3.9'` and the Gemfile
36
- `path:` override is commented out. Retained here as a record of the cutover
37
- procedure (verify the published version via the RubyGems API, update the
38
- gemspec constraint, drop the override, re-run `rake ci`) in case a similar
39
- edge-dependency bridge is needed again.
40
- - **solr-roundtrip-verify** — bring up a local Solr, run a real index + commit +
41
- delete round-trip through the pooled writer, and confirm connection reuse via
42
- registry stats (mirrors the sibling gem's `examples/solr_update_demo.rb`).