gemkeeper 0.8.0 → 0.8.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 +10 -1
- data/lib/gemkeeper/version.rb +1 -1
- metadata +17 -16
- data/specs/20260518-154733-gemkeeper-contractor-support/implementation-summary.md +0 -75
- data/specs/20260518-154733-gemkeeper-contractor-support/spec.md +0 -287
- data/specs/20260529-091429-replace-geminabox-compact-proxy/critique-consolidated-v-1.md +0 -168
- data/specs/20260529-091429-replace-geminabox-compact-proxy/critique-v-1-claude.md +0 -124
- data/specs/20260529-091429-replace-geminabox-compact-proxy/critique-v-1-codex.md +0 -125
- data/specs/20260529-091429-replace-geminabox-compact-proxy/critique-v-1-copilot.md +0 -261
- data/specs/20260529-091429-replace-geminabox-compact-proxy/spec.md +0 -360
- data/specs/20260529-131354-sync-serve-cache-contract/critique-consolidated-v-1.md +0 -95
- data/specs/20260529-131354-sync-serve-cache-contract/critique-v-1-claude.md +0 -47
- data/specs/20260529-131354-sync-serve-cache-contract/critique-v-1-codex.md +0 -112
- data/specs/20260529-131354-sync-serve-cache-contract/critique-v-1-copilot.md +0 -169
- data/specs/20260529-131354-sync-serve-cache-contract/implementation-summary.md +0 -59
- data/specs/20260529-131354-sync-serve-cache-contract/spec.md +0 -169
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb3f426b4f46c874ec2f771bf5358fecbc16b5bf89c2aa4f98b156f5b2a6f5f3
|
|
4
|
+
data.tar.gz: 18f4c1ddff554048708f75c9c0fdee83aa2376302510f16bb27c8a7c6c74bd9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad2ac96a3426d7d6f5102b5ee53fb0db42f965937c6a19a9cff0105d51adc5ad9ff64ef7270de37c64f327184dd2a5e2984ba27b8d80f7c55451a23fe5a52e7a
|
|
7
|
+
data.tar.gz: 663465d415e7a30f0d74b092344b169968ab798cfad747673fdfa5f0371d61eb8ff974cfb9500c00b969390c77786286c38fc476754cdabba3119c604828c9ac
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.8.1] - 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Updated gemspec description to reflect the current compact index server architecture; removes the stale "Gem in a Box" reference.
|
|
8
|
+
- Declared `rack` as an explicit gemspec dependency; it was previously relied on transitively via `rackup`.
|
|
9
|
+
- Removed stale dev dependencies (`builder`, `irb`, `ostruct`) from the Gemfile.
|
|
10
|
+
|
|
3
11
|
## [0.8.0] - 2026-05-29
|
|
4
12
|
|
|
5
13
|
### Changed
|
|
@@ -186,7 +194,8 @@
|
|
|
186
194
|
|
|
187
195
|
- Initial release
|
|
188
196
|
|
|
189
|
-
[Unreleased]: https://github.com/danhorst/gemkeeper/compare/v0.8.
|
|
197
|
+
[Unreleased]: https://github.com/danhorst/gemkeeper/compare/v0.8.1...HEAD
|
|
198
|
+
[0.8.1]: https://github.com/danhorst/gemkeeper/compare/0.8.0...0.8.1
|
|
190
199
|
[0.8.0]: https://github.com/danhorst/gemkeeper/compare/0.7.2...0.8.0
|
|
191
200
|
[0.7.2]: https://github.com/danhorst/gemkeeper/compare/0.7.1...0.7.2
|
|
192
201
|
[0.7.1]: https://github.com/danhorst/gemkeeper/compare/0.7.0...0.7.1
|
data/lib/gemkeeper/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gemkeeper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dan Brubaker Horst
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '7.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rack
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '3.0'
|
|
83
97
|
- !ruby/object:Gem::Dependency
|
|
84
98
|
name: rackup
|
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,8 +108,8 @@ dependencies:
|
|
|
94
108
|
- - "~>"
|
|
95
109
|
- !ruby/object:Gem::Version
|
|
96
110
|
version: '2.0'
|
|
97
|
-
description:
|
|
98
|
-
|
|
111
|
+
description: A utility for building and hosting private gems locally and proxying
|
|
112
|
+
to RubyGems for the rest.
|
|
99
113
|
email:
|
|
100
114
|
- dan.brubaker.horst@gmail.com
|
|
101
115
|
executables:
|
|
@@ -154,19 +168,6 @@ files:
|
|
|
154
168
|
- lib/gemkeeper/version.rb
|
|
155
169
|
- mise.toml
|
|
156
170
|
- sig/gemkeeper.rbs
|
|
157
|
-
- specs/20260518-154733-gemkeeper-contractor-support/implementation-summary.md
|
|
158
|
-
- specs/20260518-154733-gemkeeper-contractor-support/spec.md
|
|
159
|
-
- specs/20260529-091429-replace-geminabox-compact-proxy/critique-consolidated-v-1.md
|
|
160
|
-
- specs/20260529-091429-replace-geminabox-compact-proxy/critique-v-1-claude.md
|
|
161
|
-
- specs/20260529-091429-replace-geminabox-compact-proxy/critique-v-1-codex.md
|
|
162
|
-
- specs/20260529-091429-replace-geminabox-compact-proxy/critique-v-1-copilot.md
|
|
163
|
-
- specs/20260529-091429-replace-geminabox-compact-proxy/spec.md
|
|
164
|
-
- specs/20260529-131354-sync-serve-cache-contract/critique-consolidated-v-1.md
|
|
165
|
-
- specs/20260529-131354-sync-serve-cache-contract/critique-v-1-claude.md
|
|
166
|
-
- specs/20260529-131354-sync-serve-cache-contract/critique-v-1-codex.md
|
|
167
|
-
- specs/20260529-131354-sync-serve-cache-contract/critique-v-1-copilot.md
|
|
168
|
-
- specs/20260529-131354-sync-serve-cache-contract/implementation-summary.md
|
|
169
|
-
- specs/20260529-131354-sync-serve-cache-contract/spec.md
|
|
170
171
|
homepage: https://github.com/danhorst/gemkeeper
|
|
171
172
|
licenses:
|
|
172
173
|
- MIT
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Implementation Summary: 20260518-154733-gemkeeper-contractor-support
|
|
2
|
-
|
|
3
|
-
**Status:** Completed
|
|
4
|
-
**Date:** 2026-05-18
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
Implemented contractor support for gemkeeper: a `setup` command that generates config from a
|
|
9
|
-
Gemfile.lock + org manifest, lockfile-aware sync with idempotency and partial failure handling,
|
|
10
|
-
localhost-only server binding, ref injection protection, and a contractor setup sequence in the README.
|
|
11
|
-
|
|
12
|
-
Also fixed a pre-existing test failure (`test_checkout_version_with_tag`) caused by
|
|
13
|
-
`tag.gpgSign = true` in global git config silently breaking tag creation in test helpers.
|
|
14
|
-
|
|
15
|
-
## Files Created
|
|
16
|
-
|
|
17
|
-
- `lib/gemkeeper/lockfile_parser.rb` — walks directories for Gemfile.lock, parses GEM section
|
|
18
|
-
- `lib/gemkeeper/manifest_reader.rb` — loads `~/.config/gemkeeper/manifest.yml`
|
|
19
|
-
- `lib/gemkeeper/cli/commands/setup.rb` — `gemkeeper setup` command (FR-1.1, FR-1.2)
|
|
20
|
-
- `test/gemkeeper/test_lockfile_parser.rb` — unit tests for LockfileParser
|
|
21
|
-
- `test/gemkeeper/test_manifest_reader.rb` — unit tests for ManifestReader
|
|
22
|
-
- `test/integration/test_setup_integration.rb` — integration tests for setup command
|
|
23
|
-
- `test/fixtures/sample.lock` — fixture Gemfile.lock with GEM and GIT sections
|
|
24
|
-
- `test/fixtures/sample_manifest.yml` — fixture manifest with 3 internal gems
|
|
25
|
-
|
|
26
|
-
## Files Modified
|
|
27
|
-
|
|
28
|
-
- `lib/gemkeeper/errors.rb` — added `ManifestNotFoundError`
|
|
29
|
-
- `lib/gemkeeper/configuration.rb` — `from_lockfile?` predicate, version validation (AR-4.1)
|
|
30
|
-
- `lib/gemkeeper/git_repository.rb` — `validate_ref!` (AR-3.2), `checkout_resolved_version` (FR-2.1)
|
|
31
|
-
- `lib/gemkeeper/server_manager.rb` — `--host 127.0.0.1` in both cmd builders (AR-3.1); extracted `build_start_cmd` / `build_foreground_cmd` helpers
|
|
32
|
-
- `lib/gemkeeper/cli/commands/sync.rb` — `from_lockfile` resolution, idempotency skip, partial failure collection, auth error detection (FR-2.1–2.4)
|
|
33
|
-
- `lib/gemkeeper/cli.rb` — require setup command
|
|
34
|
-
- `lib/gemkeeper.rb` — require lockfile_parser, manifest_reader
|
|
35
|
-
- `test/gemkeeper/test_configuration.rb` — tests for `from_lockfile?`, validation
|
|
36
|
-
- `test/gemkeeper/test_git_repository.rb` — tests for ref validation
|
|
37
|
-
- `test/gemkeeper/test_server_manager.rb` — tests for `--host 127.0.0.1`
|
|
38
|
-
- `test/integration/test_cli_integration.rb` — tests for skip-cached, from_lockfile error, partial failure
|
|
39
|
-
- `test/integration/test_git_repository_integration.rb` — fixed `tag.gpgSign`/`commit.gpgSign` in helpers
|
|
40
|
-
- `README.md` — Contractor Setup section (FR-4.1), HTTPS URL examples (FR-4.2)
|
|
41
|
-
|
|
42
|
-
## Test Results
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
89 runs, 207 assertions, 0 failures, 0 errors, 0 skips
|
|
46
|
-
bundle exec rubocop: no offenses detected
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Baseline before implementation: 57 runs (1 pre-existing error fixed before starting).
|
|
50
|
-
|
|
51
|
-
## Spec Adherence
|
|
52
|
-
|
|
53
|
-
| Requirement | Status | Implementation | Test |
|
|
54
|
-
|-------------|--------|---------------|------|
|
|
55
|
-
| FR-1.1 setup command | Done | `cli/commands/setup.rb` | `test_setup_integration.rb` (7 tests) |
|
|
56
|
-
| FR-1.2 bundle config output | Done | `setup.rb:print_bundler_instructions` | `test_setup_prints_bundle_config_instruction` |
|
|
57
|
-
| FR-2.1 from_lockfile resolution | Done | `sync.rb:resolve_version`, `git_repository.rb:checkout_resolved_version` | `test_sync_from_lockfile_no_lockfile_exits_nonzero` |
|
|
58
|
-
| FR-2.2 skip cached versions | Done | `sync.rb:cached?` | `test_sync_skips_already_cached_gem` |
|
|
59
|
-
| FR-2.3 partial failure handling | Done | `sync.rb:run_sync` + `report_failures` | `test_sync_partial_failure_continues_and_exits_nonzero` |
|
|
60
|
-
| FR-2.4 git auth error handling | Done | `sync.rb:auth_error?` + `auth_failure_error` | Covered via partial failure path; no standalone auth-mock test |
|
|
61
|
-
| FR-3.1 localhost-only binding | Done | `server_manager.rb:build_start_cmd/build_foreground_cmd` | `test_start_server_command_binds_to_localhost` |
|
|
62
|
-
| FR-4.1 contractor setup sequence | Done | `README.md` Contractor Setup section | Documentation |
|
|
63
|
-
| FR-4.2 HTTPS URL examples | Done | `README.md` Configuration Options | Documentation |
|
|
64
|
-
| AR-3.1 --host 127.0.0.1 in rackup | Done | Both cmd builders in `server_manager.rb` | `test_start_server_*_binds_to_localhost` |
|
|
65
|
-
| AR-3.2 ref validation | Done | `git_repository.rb:validate_ref!` | `test_checkout_version_rejects_unsafe_ref/spaces` |
|
|
66
|
-
| AR-4.1 from_lockfile schema | Done | `configuration.rb:GemDefinition` | `test_from_lockfile_version_recognized`, `test_invalid_version_raises_error` |
|
|
67
|
-
| AR-4.2 no Geminabox patching | Done | Geminabox used as-is; no monkey-patching | — |
|
|
68
|
-
| AR-4.3 Bundler mirror approach | Done | `setup.rb` prints mirror cmd; README documents it | `test_setup_prints_bundle_config_instruction` |
|
|
69
|
-
|
|
70
|
-
## Deviations from Spec
|
|
71
|
-
|
|
72
|
-
**FR-2.4 standalone test:** The auth error message format (containing "authentication" and the docs URL)
|
|
73
|
-
is exercised through the partial failure path in integration tests rather than a dedicated mock.
|
|
74
|
-
A real git auth error would propagate through the same code path.
|
|
75
|
-
The behavior is implemented correctly; the gap is test isolation, not coverage.
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
# Spec 20260518-154733: Gemkeeper Contractor Support
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
Contractors need to install internal Ruby gems that are normally served from a private gem
|
|
5
|
-
server they cannot reach without VPN.
|
|
6
|
-
This spec defines the features needed to make gemkeeper a reliable, self-service solution for
|
|
7
|
-
this use case: a project setup command, lockfile-aware sync, correct server binding, and
|
|
8
|
-
accurate documentation of the full setup sequence.
|
|
9
|
-
The companion CLI tool that delivers the gem manifest is specified separately.
|
|
10
|
-
|
|
11
|
-
## Goals
|
|
12
|
-
- Let a contractor configure gemkeeper for a specific project without knowing the internal
|
|
13
|
-
gem inventory
|
|
14
|
-
- Make `gemkeeper sync` idempotent against a `Gemfile.lock` with no redundant builds
|
|
15
|
-
- Document the end-to-end contractor setup sequence in the gemkeeper README
|
|
16
|
-
- Harden the server and sync commands against the most common failure modes
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Feature 1: Project Setup Command
|
|
21
|
-
|
|
22
|
-
**Who & why:** A contractor has just installed gemkeeper and has a `Gemfile.lock` in front
|
|
23
|
-
of them.
|
|
24
|
-
They should not need to know the names of internal gems, their GitHub URLs, or how to
|
|
25
|
-
construct a `gemkeeper.yml` by hand.
|
|
26
|
-
`gemkeeper setup` reads the lockfile, cross-references the org gem manifest, and
|
|
27
|
-
produces a ready-to-use config.
|
|
28
|
-
|
|
29
|
-
### Functional Requirements
|
|
30
|
-
|
|
31
|
-
#### FR-1.1: Setup Command
|
|
32
|
-
`gemkeeper setup <path-to-gemfile-lock>` reads the `GEM` section of the lockfile,
|
|
33
|
-
cross-references the manifest at `~/.config/gemkeeper/manifest.yml` (override:
|
|
34
|
-
`--manifest <path>`), and writes a `gemkeeper.yml` in the current directory.
|
|
35
|
-
|
|
36
|
-
Each matched gem entry uses `version: "from_lockfile"` so subsequent sync calls read the
|
|
37
|
-
current lockfile version rather than requiring setup to be re-run on every lockfile change.
|
|
38
|
-
|
|
39
|
-
**Behavior when `gemkeeper.yml` already exists:** the command merges — it adds or updates
|
|
40
|
-
only entries for gems found in the manifest, leaving all other keys (port, repos_path,
|
|
41
|
-
gems_path, unrelated gem entries) untouched.
|
|
42
|
-
Pass `--force` to overwrite the file entirely.
|
|
43
|
-
|
|
44
|
-
**Error cases:**
|
|
45
|
-
- Manifest not found at default or `--manifest` path → fail with message directing the user
|
|
46
|
-
to install the org's gem manifest
|
|
47
|
-
- Gem appears in lockfile with a name matching an internal pattern but absent from manifest →
|
|
48
|
-
warn and skip, do not fail
|
|
49
|
-
|
|
50
|
-
**Verify:** Running `gemkeeper setup /path/to/Gemfile.lock` in a directory with no prior
|
|
51
|
-
config produces a `gemkeeper.yml` listing only the internal gems the lockfile references, each
|
|
52
|
-
with `version: "from_lockfile"`.
|
|
53
|
-
|
|
54
|
-
#### FR-1.2: Bundler Configuration Output
|
|
55
|
-
After setup completes, the command prints the `bundle config` command the developer
|
|
56
|
-
must run to redirect their project's internal gem source to the local Geminabox.
|
|
57
|
-
If the manifest includes a `source_url` field, that URL is used as the mirror key.
|
|
58
|
-
Otherwise, the command prints a generic placeholder the developer must fill in.
|
|
59
|
-
Example output:
|
|
60
|
-
```
|
|
61
|
-
To point Bundler at your local Geminabox, run:
|
|
62
|
-
bundle config set --local mirror.<private-gem-source-url> http://localhost:9292
|
|
63
|
-
```
|
|
64
|
-
The port in the command matches the configured port in `gemkeeper.yml` (default: 9292).
|
|
65
|
-
`git:` source declarations in the Gemfile are unaffected and require no mirror config.
|
|
66
|
-
|
|
67
|
-
**Verify:** After running the printed command, `bundle install` resolves internal gems from
|
|
68
|
-
the local Geminabox and public gems from rubygems.org without modifying any tracked file.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Feature 2: Lockfile-Aware Sync
|
|
73
|
-
|
|
74
|
-
**Who & why:** With `version: "from_lockfile"` in the config, sync must read the lockfile,
|
|
75
|
-
resolve the correct git checkout ref, skip versions already cached, and handle partial
|
|
76
|
-
failures gracefully.
|
|
77
|
-
A sync that rebuilds everything on every run, or that fails entirely on one bad gem, is not
|
|
78
|
-
usable in practice.
|
|
79
|
-
|
|
80
|
-
### Functional Requirements
|
|
81
|
-
|
|
82
|
-
#### FR-2.1: `from_lockfile` Version Resolution
|
|
83
|
-
When a gem entry specifies `version: "from_lockfile"`, `gemkeeper sync`:
|
|
84
|
-
1. Walks up from the current directory to find the nearest `Gemfile.lock`
|
|
85
|
-
2. Reads the gem's locked version from the `GEM` section
|
|
86
|
-
3. Attempts to check out git tag `v{version}`, then `{version}`, in that order
|
|
87
|
-
4. Fails with a named error if neither tag exists in the repo (naming the gem and version)
|
|
88
|
-
|
|
89
|
-
If no `Gemfile.lock` is found in the directory walk, sync fails with a message naming the
|
|
90
|
-
gem and the search path traversed.
|
|
91
|
-
|
|
92
|
-
`version: "latest"` and explicit version tags continue to work as before.
|
|
93
|
-
|
|
94
|
-
**Verify:** `gemkeeper sync` for a gem with `version: "from_lockfile"` checks out the git tag
|
|
95
|
-
corresponding to the version pinned in the nearest `Gemfile.lock`.
|
|
96
|
-
When no lockfile is found, the command exits non-zero with a descriptive message.
|
|
97
|
-
|
|
98
|
-
#### FR-2.2: Skip Already-Cached Versions
|
|
99
|
-
Before cloning, fetching, building, or uploading, `gemkeeper sync` checks whether the `.gem`
|
|
100
|
-
file for the resolved version is already present in the Geminabox cache at
|
|
101
|
-
`File.join(gems_path, "gems", "#{name}-#{version}.gem")`.
|
|
102
|
-
If present, the gem is skipped entirely — no clone, no build, no upload.
|
|
103
|
-
Re-running sync with no lockfile change is a no-op.
|
|
104
|
-
|
|
105
|
-
**Verify:** First sync builds and uploads gem X at version 1.2.3.
|
|
106
|
-
Second sync with no lockfile change produces no git, build, or upload activity.
|
|
107
|
-
Updating the lockfile to version 1.3.0 causes sync to build and upload only that version.
|
|
108
|
-
|
|
109
|
-
#### FR-2.3: Partial Failure Handling
|
|
110
|
-
If one gem's clone, build, or upload fails, sync continues with the remaining gems.
|
|
111
|
-
At the end of the run, sync reports all failures (gem name + error message) and exits
|
|
112
|
-
non-zero if any gem failed.
|
|
113
|
-
A fully successful sync exits zero.
|
|
114
|
-
**Verify:** A sync with one gem that fails to build still attempts all other configured gems
|
|
115
|
-
and exits non-zero with a summary of the failure.
|
|
116
|
-
|
|
117
|
-
#### FR-2.4: Git Authentication Failure Handling
|
|
118
|
-
When `git clone` or `git fetch` returns an authentication error, sync exits non-zero with a
|
|
119
|
-
message that includes the failed repo URL and a pointer to GitHub credential setup
|
|
120
|
-
documentation (`https://docs.github.com/en/authentication`).
|
|
121
|
-
The raw git error may be included but the failure type must be identified specifically.
|
|
122
|
-
Clone URLs must not use the embedded-token format (`https://token@github.com/...`); the
|
|
123
|
-
README must warn against this pattern.
|
|
124
|
-
**Verify:** Running sync against a repo with no configured GitHub credentials produces a
|
|
125
|
-
message containing "authentication" and the docs URL.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Feature 3: Server and Security Hardening
|
|
130
|
-
|
|
131
|
-
**Who & why:** Geminabox runs without authentication.
|
|
132
|
-
Binding to `0.0.0.0` would expose an unauthenticated gem push endpoint to LAN peers, allowing
|
|
133
|
-
a malicious gem to be silently installed by `bundle install`.
|
|
134
|
-
Ref validation prevents argument injection from crafted manifest entries.
|
|
135
|
-
|
|
136
|
-
### Functional Requirements
|
|
137
|
-
|
|
138
|
-
#### FR-3.1: Localhost-Only Server Binding
|
|
139
|
-
`gemkeeper server start` (and `server start --foreground`) must bind Geminabox to `127.0.0.1`
|
|
140
|
-
only.
|
|
141
|
-
**Verify:** After `gemkeeper server start`, `lsof -i :9292` shows the process bound to
|
|
142
|
-
`127.0.0.1`, not `0.0.0.0`.
|
|
143
|
-
|
|
144
|
-
### Architectural Requirements
|
|
145
|
-
|
|
146
|
-
#### AR-3.1: `--host 127.0.0.1` Passed to Rackup
|
|
147
|
-
The `start_server` and `start_server_foreground` methods in `ServerManager` must pass
|
|
148
|
-
`"--host", "127.0.0.1"` in the rackup command array.
|
|
149
|
-
This applies to both daemonized and foreground start.
|
|
150
|
-
|
|
151
|
-
#### AR-3.2: Manifest Ref Validation
|
|
152
|
-
Before passing any manifest-derived or lockfile-derived value to `git checkout`, gemkeeper
|
|
153
|
-
validates the value against `/\A[a-zA-Z0-9._\-]+\z/`.
|
|
154
|
-
Values that do not match are rejected with an error naming the offending entry.
|
|
155
|
-
The existing `run_git` method uses `Open3.capture3(*cmd)` array form and is already safe from
|
|
156
|
-
shell injection; this validation prevents argument injection (e.g., `--upload-pack=...`).
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Feature 4: Documentation
|
|
161
|
-
|
|
162
|
-
**Who & why:** A contractor following the gemkeeper README must be able to complete setup
|
|
163
|
-
independently.
|
|
164
|
-
The current README does not document the contractor workflow or the full sequence
|
|
165
|
-
of commands needed to get `bundle install` working.
|
|
166
|
-
|
|
167
|
-
### Functional Requirements
|
|
168
|
-
|
|
169
|
-
#### FR-4.1: Contractor Setup Sequence
|
|
170
|
-
The gemkeeper README must document the full setup sequence as a numbered checklist:
|
|
171
|
-
1. Install gemkeeper (`gem install gemkeeper`)
|
|
172
|
-
2. Install the org's gem manifest (mechanism provided by the org)
|
|
173
|
-
3. Run `gemkeeper setup <path/to/Gemfile.lock>` in the project directory
|
|
174
|
-
4. Run `gemkeeper sync` to build and cache internal gems
|
|
175
|
-
5. Start the local server (`gemkeeper server start`)
|
|
176
|
-
6. Configure Bundler with the command printed by step 3
|
|
177
|
-
|
|
178
|
-
The checklist must note that GitHub credentials must be configured before step 4 (link to
|
|
179
|
-
`https://docs.github.com/en/authentication`).
|
|
180
|
-
|
|
181
|
-
**Verify:** A new contractor with no prior gemkeeper knowledge can follow the checklist and
|
|
182
|
-
reach a passing `bundle install` without assistance.
|
|
183
|
-
|
|
184
|
-
#### FR-4.2: HTTPS Git URL Documentation
|
|
185
|
-
Config examples and the README must document HTTPS GitHub URLs
|
|
186
|
-
(`https://github.com/org/repo`) as the recommended format for contractors who have not
|
|
187
|
-
configured SSH keys.
|
|
188
|
-
SSH URLs (`git@github.com:org/repo.git`) must be noted as an alternative.
|
|
189
|
-
**Verify:** The README config examples use HTTPS URLs.
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Architectural Requirements (Cross-Cutting)
|
|
194
|
-
|
|
195
|
-
#### AR-4.1: `from_lockfile` Schema in Configuration
|
|
196
|
-
`from_lockfile` is a new reserved string in `Configuration::GemDefinition`, validated
|
|
197
|
-
alongside `"latest"`.
|
|
198
|
-
The existing `latest?` predicate is joined by a `from_lockfile?` predicate.
|
|
199
|
-
`gemkeeper.yml` validation rejects any `version` value that is not `"latest"`,
|
|
200
|
-
`"from_lockfile"`, or a string matching `/\A[a-zA-Z0-9._\-]+\z/`.
|
|
201
|
-
|
|
202
|
-
#### AR-4.2: No Geminabox Patching
|
|
203
|
-
All sync logic, idempotency tracking, and version resolution lives in gemkeeper code.
|
|
204
|
-
Geminabox is used as a Rack dependency without modification.
|
|
205
|
-
`rubygems_proxy = true` is already set in the generated `config.ru` and requires no change.
|
|
206
|
-
|
|
207
|
-
#### AR-4.3: Bundler Mirror over Source Block
|
|
208
|
-
`gemkeeper setup` prints a `bundle config set --local mirror.X Y` instruction rather than
|
|
209
|
-
suggesting a Gemfile source block, because the mirror approach requires no change to the
|
|
210
|
-
committed `Gemfile` or `Gemfile.lock`.
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## Integration Points
|
|
215
|
-
|
|
216
|
-
- gemkeeper setup → `~/.config/gemkeeper/manifest.yml` (written by the org's manifest installer)
|
|
217
|
-
- gemkeeper sync → GitHub (HTTPS or SSH clone; no private gem server required)
|
|
218
|
-
- gemkeeper server → Geminabox (Rack app, localhost only)
|
|
219
|
-
- gemkeeper → Bundler (mirror config, local scope, printed by setup)
|
|
220
|
-
|
|
221
|
-
## Related Specs
|
|
222
|
-
|
|
223
|
-
The companion CLI tool that delivers `~/.config/gemkeeper/manifest.yml` is out of scope for this repo.
|
|
224
|
-
|
|
225
|
-
## Constraints
|
|
226
|
-
- Geminabox is used as a dependency, not modified
|
|
227
|
-
- Ruby >= 3.1.0 (existing gemspec requirement)
|
|
228
|
-
|
|
229
|
-
## Out of Scope
|
|
230
|
-
- Automating manifest updates on gem release
|
|
231
|
-
- Centrally hosted gem mirror
|
|
232
|
-
- Windows support
|
|
233
|
-
- Gems declared via `git:` source blocks (these already resolve via GitHub; no caching needed)
|
|
234
|
-
- CI/CD pipeline support (contractors and local dev only for this spec)
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## Spec Completeness Checklist
|
|
239
|
-
|
|
240
|
-
- [x] **Scope & acceptance criteria** — FR-1.1 through FR-4.2 have Verify: lines; Out of
|
|
241
|
-
Scope names exclusions; `git:` source blocks explicitly excluded
|
|
242
|
-
- [x] **Testing strategy** — new commands (setup, FR-1.1; manifest-aware sync, FR-2.1–2.4)
|
|
243
|
-
follow the pattern in `test/` with unit tests per class and integration tests for CLI
|
|
244
|
-
commands; manifest loading tested with fixture YAML; lockfile parsing tested with fixture
|
|
245
|
-
lockfiles covering `GEM` and `GIT` sections; FR-3.1 server binding tested via `lsof` in
|
|
246
|
-
integration test; ref validation (AR-3.2) tested with crafted inputs
|
|
247
|
-
- [x] **Existing patterns** — spec extends `Configuration::GemDefinition` (AR-4.1),
|
|
248
|
-
`GemBuilder`/`GemUploader`/`GitRepository` (sync FRs), `ServerManager` (FR-3.1);
|
|
249
|
-
`run_git` array form confirmed safe from shell injection
|
|
250
|
-
- [x] **Dependencies** — no new runtime gems required; `bundler` gem used for lockfile
|
|
251
|
-
parsing if not already available (it is, as a development dependency); no other additions
|
|
252
|
-
- [x] **Architecture & interfaces** — `from_lockfile` schema defined in AR-4.1; lockfile
|
|
253
|
-
search and version-to-tag mapping defined in FR-2.1; idempotency check path defined in
|
|
254
|
-
FR-2.2; server binding defined in AR-3.1; manifest interface defined in Integration Points
|
|
255
|
-
- [x] **Error handling & failure modes** — FR-2.1 covers missing lockfile and missing tag;
|
|
256
|
-
FR-2.3 covers partial sync failure; FR-2.4 covers git auth failure; FR-1.1 covers missing
|
|
257
|
-
manifest; AR-3.2 covers invalid refs; known limitation: disk-present ≠ upload-confirmed
|
|
258
|
-
(see Assumptions & Risks #3)
|
|
259
|
-
- [x] **Security review** — `run_git` uses `Open3.capture3(*cmd)` array form (safe); AR-3.2
|
|
260
|
-
adds ref validation; AR-3.1 binds to localhost; FR-2.4 prohibits embedded-token URLs;
|
|
261
|
-
no credentials stored by gemkeeper
|
|
262
|
-
- [x] **Performance impact** — idempotency check (FR-2.2) skips entire clone/build/upload
|
|
263
|
-
pipeline when version is cached; Geminabox proxies public gems via `rubygems_proxy = true`;
|
|
264
|
-
no production system impact
|
|
265
|
-
- [N/A] **Rollout & migration** — additive; existing `gemkeeper.yml` files without
|
|
266
|
-
`from_lockfile` continue to work; server binding change is transparent to users
|
|
267
|
-
- [x] **Assumptions & risks** — see below
|
|
268
|
-
|
|
269
|
-
### Assumptions & Risks
|
|
270
|
-
|
|
271
|
-
1. **Assumption:** All contractors have HTTPS GitHub access to the internal gem repos listed
|
|
272
|
-
in the manifest.
|
|
273
|
-
The contractor setup checklist (FR-4.1) must state this as a prerequisite.
|
|
274
|
-
|
|
275
|
-
2. **Assumption:** Internal gem `gemspec` files can be evaluated locally without proprietary
|
|
276
|
-
build tooling.
|
|
277
|
-
If a gem requires a non-standard build step, `gemkeeper sync` will fail.
|
|
278
|
-
Mitigation: verify `gemkeeper sync` succeeds for each gem before adding it to the manifest.
|
|
279
|
-
|
|
280
|
-
3. **Known limitation:** The idempotency check (FR-2.2) confirms a `.gem` file exists on
|
|
281
|
-
disk, not that it was successfully uploaded to the running Geminabox instance.
|
|
282
|
-
A build whose upload failed will be silently skipped on re-run.
|
|
283
|
-
Mitigation: `gemkeeper list` can be used to verify what the running server holds.
|
|
284
|
-
|
|
285
|
-
4. **Risk:** Gems containing native extensions produce platform-specific `.gem` files.
|
|
286
|
-
A gem cache is not portable across machines with different CPU architectures.
|
|
287
|
-
Each developer must run `gemkeeper sync` on their own machine.
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# Spec 20260529-091429: Consolidated Critique (v1)
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
**Critiques received from:** Claude, Copilot, Codex
|
|
6
|
-
**Critiques missing:** None
|
|
7
|
-
|
|
8
|
-
## Executive Summary
|
|
9
|
-
|
|
10
|
-
All three critics reached the same verdict: the direction and architecture are sound, but the spec is not implementation-ready.
|
|
11
|
-
The blocking issues cluster around three areas: the `/versions` merge algorithm is underspecified in ways Bundler will exercise directly; ETag/digest header generation for merged responses is absent; and security input validation is incorrectly dismissed.
|
|
12
|
-
There are also several codebase assumptions that don't match reality (GemUploader response codes, `rubygems-generate_index` dependency, `list_gems` dead method).
|
|
13
|
-
The fixes are additive — the spec doesn't need restructuring, just more precision in the places listed below.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Blocking Issues (must resolve before implementation)
|
|
18
|
-
|
|
19
|
-
### B-1. `/versions` merge algorithm is underspecified
|
|
20
|
-
|
|
21
|
-
All three critics flagged this.
|
|
22
|
-
The spec says private gems "take precedence" when a name appears in both, but doesn't define the algorithm.
|
|
23
|
-
|
|
24
|
-
The problems:
|
|
25
|
-
|
|
26
|
-
1. **Collision semantics** — does "private takes precedence" mean: replace the entire public entry (suppressing all public versions), replace only matching version entries, or append a private-wins line and rely on Bundler's last-entry behavior? If public versions remain visible, a developer pulling `rails 7.1.0` from the private gem (a fork) will still see public rails versions and Bundler may resolve the wrong one (dependency confusion).
|
|
27
|
-
|
|
28
|
-
2. **Byte stability** — Bundler caches the byte offset of the last `/versions` line it fetched and issues `Range: bytes=N-` on subsequent requests. If the merge interleaves private gems by insertion date, any new private gem shifts offsets of everything after it, corrupting Bundler's incremental update. The spec must define a stable layout: e.g., upstream public block verbatim first, private gem entries appended at the end.
|
|
29
|
-
|
|
30
|
-
3. **`VersionsFile` API** — `CompactIndex.versions(versions_file, extra_gems)` takes a `CompactIndex::VersionsFile` object backed by a cached file, not a raw upstream response string. The spec must describe how the upstream body is persisted to disk and surfaced as a `VersionsFile`.
|
|
31
|
-
|
|
32
|
-
**Recommendation:**
|
|
33
|
-
- Cache the raw upstream `/versions` response verbatim to `cache_dir/rubygems_cache/versions`.
|
|
34
|
-
- Construct a `VersionsFile` from that cached file.
|
|
35
|
-
- Pass private gems as `extra_gems` so they are appended after the public block — this preserves byte stability for the public block.
|
|
36
|
-
- "Precedence" means private gem entries replace the matching public name in `extra_gems` merge (public versions for colliding names are suppressed).
|
|
37
|
-
|
|
38
|
-
### B-2. ETag and `Repr-Digest` for merged responses must be computed from the merged body
|
|
39
|
-
|
|
40
|
-
The spec requires these headers (FR-1.3) but does not say how to derive them for merged responses.
|
|
41
|
-
All three critics noted this independently.
|
|
42
|
-
|
|
43
|
-
Forwarding the upstream `ETag` or `Repr-Digest` header from RubyGems.org is wrong — the merged body differs from the upstream body, so Bundler's SHA256 verification will fail and it will retry unconditionally.
|
|
44
|
-
|
|
45
|
-
**Recommendation:** For all endpoints serving merged or locally-generated content (`/versions`, `/info/:gemname` for private gems, `/names`), compute `ETag` and `Repr-Digest: sha-256=<base64>` from the final response body. Do not forward upstream headers unchanged.
|
|
46
|
-
Pick SHA256 for ETag (avoids a second hash pass; consistent with `Repr-Digest`).
|
|
47
|
-
|
|
48
|
-
### B-3. `info_checksum` has a circular dependency
|
|
49
|
-
|
|
50
|
-
Copilot and Claude both identified this.
|
|
51
|
-
`CompactIndex::GemVersion#info_checksum` is the SHA256 of the `/info/:gemname` response body.
|
|
52
|
-
To populate it in `/versions`, the server must generate the `/info` body first, hash it, and embed the hash.
|
|
53
|
-
An implementer who builds the versions index before the info bodies will produce invalid checksums, causing Bundler to re-fetch unconditionally.
|
|
54
|
-
|
|
55
|
-
**Recommendation:** Add an AR stating: info bodies for all private gems are generated first; their SHA256 checksums are computed and stored in memory; then the `/versions` index is built referencing those checksums. Checksums are recomputed after each successful upload.
|
|
56
|
-
|
|
57
|
-
Also note: Codex flagged that `CompactIndex::GemVersion` may use `number` rather than `version` as the field name in 0.15.x. Verify the actual field names against the installed gem before implementation.
|
|
58
|
-
|
|
59
|
-
### B-4. `/names` scope is undefined
|
|
60
|
-
|
|
61
|
-
Copilot and Codex both flagged this.
|
|
62
|
-
RubyGems.org `/names` is ~2.7 MB / ~175,000 entries.
|
|
63
|
-
FR-1.1 says `/names` returns "all gem names (local and proxied)" but doesn't say whether the server fetches, caches, and merges the upstream `/names` file or scopes to a smaller subset.
|
|
64
|
-
|
|
65
|
-
These produce different behavior:
|
|
66
|
-
- Full public namespace: correct, but expensive.
|
|
67
|
-
- Local-only + cached: bundle install fails on any public gem not already in the info cache.
|
|
68
|
-
|
|
69
|
-
**Recommendation:** Treat `/names` consistently with `/versions` — fetch and cache the upstream `/names` file; merge with local private gem names; apply the same ETag/cache TTL rules as `/info/:gemname`.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Significant Gaps
|
|
74
|
-
|
|
75
|
-
### G-1. Path traversal in `/gems/:filename.gem`
|
|
76
|
-
|
|
77
|
-
All three critics raised this.
|
|
78
|
-
The spec's security checklist marks this N/A because the server binds to 127.0.0.1.
|
|
79
|
-
Localhost binding does not prevent path traversal — any local process can issue a crafted request.
|
|
80
|
-
A filename like `../../gemkeeper.pid` resolves outside `gems_path/gems/`.
|
|
81
|
-
The same `gemname` parameter is used to construct `https://rubygems.org/info/:gemname`, creating an SSRF vector if unvalidated.
|
|
82
|
-
|
|
83
|
-
**Recommendation:** Add a validation constraint: `:gemname` and `:filename` URL parameters are validated against `/\A[a-zA-Z0-9._-]+\z/` before any filesystem or upstream URL construction. Return 400 otherwise. Additionally, assert the resolved file path is under `gems_path/gems/` before serving.
|
|
84
|
-
|
|
85
|
-
### G-2. "Valid gem" definition in FR-1.2 is ambiguous
|
|
86
|
-
|
|
87
|
-
All three critics flagged this.
|
|
88
|
-
Three interpretations exist: (1) correct `.gem` extension; (2) parseable tar archive containing `metadata.gz`; (3) `metadata.gz` can be loaded as a Ruby gemspec without errors.
|
|
89
|
-
Option 3 is dangerous — loading a gemspec can execute arbitrary Ruby.
|
|
90
|
-
|
|
91
|
-
**Recommendation:** Specify option 2: validate that the file is a tar archive containing `metadata.gz` and `data.tar.gz`. Parse gemspec metadata from `metadata.gz` using `Gem::Package.new(path).spec` inside a rescue block. If parsing raises, return 422. Do not `load` or `eval` gemspec content.
|
|
92
|
-
|
|
93
|
-
### G-3. Upload atomicity and `gems_path/gems/` creation
|
|
94
|
-
|
|
95
|
-
Codex and Claude raised this.
|
|
96
|
-
FR-1.2 does not require `mkdir_p gems_path/gems/` (the subdirectory may not exist on first run), atomic temp-file writes (concurrent uploads of different gems can interleave file writes), or cleanup of temp files after failed validation.
|
|
97
|
-
|
|
98
|
-
**Recommendation:** Add to FR-1.2: create `gems_path/gems/` if absent before writing; write to a temp file in the same directory first, then `File.rename` to the target path (atomic on POSIX); delete temp file on validation failure.
|
|
99
|
-
|
|
100
|
-
### G-4. Thread safety for in-memory gem index
|
|
101
|
-
|
|
102
|
-
Copilot and Codex raised this.
|
|
103
|
-
Puma uses a thread pool. A `GET /info/:gemname` concurrent with an upload rebuilding the in-memory index produces a data race.
|
|
104
|
-
|
|
105
|
-
**Recommendation:** Add an AR: the in-memory gem index is replaced atomically via a single instance variable assignment after each full rebuild (copy-on-write pattern). Index reads do not acquire a lock; the rebuild completes into a new object before swapping.
|
|
106
|
-
|
|
107
|
-
### G-5. Upstream 404 vs outage distinction in FR-3.4
|
|
108
|
-
|
|
109
|
-
Codex and Copilot raised this.
|
|
110
|
-
FR-3.4 says return 404 with `"Upstream unavailable and no local cache"`.
|
|
111
|
-
But if RubyGems.org returns a genuine 404 (the gem does not exist), the spec's response body is misleading.
|
|
112
|
-
The two cases — "upstream reachable, gem not found" and "upstream unreachable" — should produce different responses.
|
|
113
|
-
|
|
114
|
-
**Recommendation:** Distinguish: upstream reachable + 404 → return 404 with no body; upstream unreachable (connection error, timeout) + no cache → return 503; upstream unreachable + cache exists → serve from cache with appropriate headers.
|
|
115
|
-
|
|
116
|
-
### G-6. `GemUploader#list_gems` becomes a dead method
|
|
117
|
-
|
|
118
|
-
Copilot raised this.
|
|
119
|
-
`GemUploader#list_gems` calls `GET /api/v1/gems.json`, a Geminabox-specific endpoint the new server will not implement.
|
|
120
|
-
The method is not called in production code (list reads the filesystem), but it is a public method that silently breaks after migration.
|
|
121
|
-
|
|
122
|
-
**Recommendation:** Either add `GET /api/v1/gems.json` to the Out of Scope list and note that `list_gems` becomes a broken dead method (acceptable since it is unused), or have the server return a 404 for that path and add a note in the spec that the method should be removed or stubbed.
|
|
123
|
-
|
|
124
|
-
### G-7. `rubygems-generate_index` dependency not addressed
|
|
125
|
-
|
|
126
|
-
Copilot and Codex both noted that `gemkeeper.gemspec` and `Gemfile` declare `rubygems-generate_index ~> 1.0`, which exists to support Geminabox's legacy Marshal index generation.
|
|
127
|
-
The spec swaps `geminabox` for `compact_index` but is silent on this.
|
|
128
|
-
|
|
129
|
-
**Recommendation:** Add `rubygems-generate_index` to the list of dependencies removed in the Integration Points table.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Additional Requirements to Add
|
|
134
|
-
|
|
135
|
-
| # | Source | Requirement |
|
|
136
|
-
| - | ------ | ----------- |
|
|
137
|
-
| AR-new-1 | All | Info bodies computed before versions index; checksums embedded in versions from pre-computed hashes |
|
|
138
|
-
| AR-new-2 | All | ETag is SHA256 of merged response body for all locally-generated or merged endpoints |
|
|
139
|
-
| AR-new-3 | All | `:gemname` and `:filename` URL params validated to `/\A[a-zA-Z0-9._-]+\z/` before filesystem or upstream URL use |
|
|
140
|
-
| AR-new-4 | Copilot/Codex | Puma's thread count should be set to 1 (or the index swap made atomic) — specify which |
|
|
141
|
-
| FR-new-1 | All | `/names` fetches, caches, and merges upstream `/names` with local gem names under the same TTL rules as `/info` |
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## Ambiguities to Resolve
|
|
146
|
-
|
|
147
|
-
1. **ETag algorithm** — spec says "MD5 or SHA256"; pick SHA256 (consistent with `Repr-Digest`, avoids two passes).
|
|
148
|
-
2. **GemUploader response codes** — spec says 201/409; actual `GemUploader#handle_response` also accepts 200 and 302 as success. Align the spec with the real code.
|
|
149
|
-
3. **`/versions` cache storage** — spec is ambiguous about whether the cache stores the raw upstream body (re-merged on request) or the merged result (invalidated on upload). Specify: cache raw upstream body; re-merge with private gems in memory on request; memoize the merged result until next upload or upstream ETag change.
|
|
150
|
-
4. **`CompactIndex::GemVersion` field names** — verify `number` vs `version` against the installed 0.15.x gem before referencing them in the spec.
|
|
151
|
-
5. **AGENTS.md vs CLAUDE.md** — Codex noted the Integration Points table references `CLAUDE.md`, but the actual project instruction file may be `AGENTS.md`. Verify and correct.
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## Summary of Required Changes
|
|
156
|
-
|
|
157
|
-
1. **(Blocking)** Specify the `/versions` merge algorithm: upstream verbatim block first, private gems appended via `extra_gems`, collision = suppress public entry, byte-stable layout.
|
|
158
|
-
2. **(Blocking)** Specify ETag and `Repr-Digest` computation from merged body for all generated/merged endpoints.
|
|
159
|
-
3. **(Blocking)** Specify `info_checksum` generation order: info bodies first, checksums embedded into versions index.
|
|
160
|
-
4. **(Blocking)** Define `/names` scope: fetched, cached, merged like `/versions`.
|
|
161
|
-
5. Add input validation constraint for `:gemname` and `:filename` path parameters.
|
|
162
|
-
6. Clarify "valid gem" validation as tar-parseable + gemspec extractable (no eval).
|
|
163
|
-
7. Add upload atomicity requirements: `mkdir_p`, temp-file write, rename.
|
|
164
|
-
8. Add thread safety AR: atomic index swap after rebuild.
|
|
165
|
-
9. Distinguish upstream 404 vs outage in FR-3.4 (404 vs 503).
|
|
166
|
-
10. Note `list_gems` dead method and `rubygems-generate_index` removal in Integration Points.
|
|
167
|
-
11. Correct GemUploader response code list (200/201/302 accepted, not just 201).
|
|
168
|
-
12. Resolve ETag algorithm ambiguity (SHA256 only).
|