Skip to content

fix(ruby): JwksStore::JWKS_MAX_AGE_SECONDS 를 게시 전에 private_constant 로 — 1.4.1 을 patch 로 - #914

Merged
xzawed merged 1 commit into
mainfrom
fix/ruby-jwks-max-age-private
Oct 31, 2026
Merged

xzawed merged 1 commit into
mainfrom
fix/ruby-jwks-max-age-private

Conversation

@xzawed

@xzawed xzawed commented Oct 31, 2026

Copy link
Copy Markdown
Owner

무엇

KeycloakSdk::JwksStore::JWKS_MAX_AGE_SECONDS(키셋 수명 300 초, #881–#900 에서 추가)를 private_constant 로 좁힌다. 릴리스 웨이브 7 의 버전 판정에서 ruby 의 1.4.0 이후 유일한 공개 표면 추가로 잡혔다.

왜

  • 나머지 여덟 언어는 같은 값을 비공개로 둔다(go 소문자 · .NET·kotlin internal · php private const · java package-private · rust 비-pub · node 비-export · python _internal).
  • 공개로 게시하면 상수 하나 때문에 이번 릴리스가 minor 가 되고, 나중에 지우면 major 다. 선례: fix(ruby): admin 미들웨어 SettleBody 를 게시 전에 private_constant 로 #854 → ruby 1.3.1.
  • 1.4.0 에는 없던 상수라 소비자 영향은 없다 → [no-changelog: …].

근거

계측 결과
리플렉션 워커 양성 대조 1.3.2 → 1.4.0 Http.settling · Http.read_error_body 추가가 보인다
음성 대조 1.4.0 → 1.4.0 0 줄
판정 1.4.0 → main(5bc8bd3) > CONST KeycloakSdk::JwksStore::JWKS_MAX_AGE_SECONDS 1 줄
1.4.0 → 이 브랜치 0 줄
main → 이 브랜치 < CONST …::JWKS_MAX_AGE_SECONDS 1 줄
RED(수정 전 새 spec) expected [...:JWKS_MAX_AGE_SECONDS...] not to include :JWKS_MAX_AGE_SECONDS
GREEN rspec 605 examples, 0 failures · rubocop no offenses
scripts/test/test-security-defaults.sh 487 passed, 0 failed (수명 축은 원문 = <숫자> 선언을 읽어 영향 없음)

🤖 Generated with Claude Code

https://claude.ai/code/session_01R4jYS4tpj7usahf8MK5dXo

… — 1.4.0 이후 생긴 유일한 공개 표면이라 1.4.1 을 patch 로 지킨다

키셋 수명 상수(#881–#900 에서 추가)가 ruby 에서만 공개 상수였다. 나머지 여덟은 비공개다 —
go `jwksMaxAgeSeconds`(소문자) · .NET `internal const` · kotlin `internal const val` · php `private const` ·
java package-private `static final` · rust 비-pub `const` · node 비-export `const` · python `_internal` 모듈.
공개로 게시하면 상수 하나가 계약이 되어 이번 릴리스가 minor 가 되고, 나중에 지우면 major 다(선례 #854 · ruby 1.3.1).

계측(리플렉션 워커 — `Module#constants` 기반이라 private_constant 를 뺀다. yard 는 private_constant 모듈을 공개로 센다):
- 양성 대조 1.3.2 → 1.4.0: Http.settling · Http.read_error_body 추가가 보인다
- 음성 대조 1.4.0 → 1.4.0: 0 줄
- 판정 1.4.0 → main(5bc8bd3): `> CONST KeycloakSdk::JwksStore::JWKS_MAX_AGE_SECONDS` 한 줄
- 이 커밋 1.4.0 → fix: 0 줄 · main → fix: `< CONST KeycloakSdk::JwksStore::JWKS_MAX_AGE_SECONDS` 한 줄

TDD:
- RED(수정 전): expected [:JWKS_MAX_BYTES, :FAILURE_BACKOFF_BASE, :JWKS_MAX_AGE_SECONDS, :FAILURE_BACKOFF_CAP]
  not to include :JWKS_MAX_AGE_SECONDS
- GREEN: bundle exec rspec → 605 examples, 0 failures · rubocop → no offenses
- scripts/test/test-security-defaults.sh → 487 passed, 0 failed (수명 축은 원문의 `= <숫자>` 선언을 읽는다 —
  새 `private_constant` 줄은 SD_TC_DECL 에 걸리지 않아 선언 수는 그대로 1)

[no-changelog: 게시 전 공개 범위 축소 — 1.4.0 이후 추가돼 미게시, 소비자 영향 없음]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4jYS4tpj7usahf8MK5dXo
@sonarqubecloud

Copy link
Copy Markdown

@xzawed
xzawed merged commit d7d353a into main Oct 31, 2026
41 checks passed
@xzawed
xzawed deleted the fix/ruby-jwks-max-age-private branch October 31, 2026 02:40
xzawed added a commit that referenced this pull request Oct 31, 2026
…4 · rust 1.1.5 · kotlin 1.0.8 · python·dotnet·node 1.0.7 (#916)

DEPLOY.md §4 step 1 — 태그 **전** 커밋(틀: 57a112c/#855, PHP 자리는 8c7ba42/#851). SSOT·매니페스트·랜딩 README·함대 문서·
CHANGELOG 절과 그 회전을 한 커밋에서 옮긴다. 바탕은 3ca15f3(#915 — [Unreleased] PHP 항목의 마지막 문장을 바로잡았다; 옮긴 항목은
그 바이트다). `.github/release-request.json` 은 건드리지 않았다(이 커밋은 태그를 자르지 않는다). `df_api_baseline` 과 기준선 사본
(`japicmp.baseline` · kotlin·python·php·ruby·node CI · `PackageValidationBaselineVersion`)도 그대로다 — 기준선은 게시 확인 뒤
(step 9)에만 움직인다. 번호는 PM 결정이다 — 아홉 모두 patch, 날짜는 모두 2026-10-31.

판정 근거(PM 이 넘긴 것):
- java 1.2.4 (patch) — v1.2.3..: 바뀐 출하 파일 15(주석만 9) · `public` 이 든 추가 줄 4 는 전부 패키지 전용 클래스의 멤버(AdminEngine 의 중첩 Exchange/Settled · BoundedTransport 안 익명 클래스) · 공개 JwtValidator 에는 패키지 전용·private 멤버만 늘었다 · TokenResponseGuard 는 패키지 전용 · 공개 타입의 interface/record/enum 변화 0. ⚠️ PM 의 첫 추출은 pathspec `java/*/src/main` 이 파일 0 개와 맞아 눈멀었다(kotlin 대조 12 = 12) — `:(glob)java/*/src/main/**` 로 다시 쟀다.
- kotlin 1.0.8 (patch) — kotlin-v1.0.7..: `public` 선언 줄 추가·삭제 0(explicitApi).
- python 1.0.7 (patch) — 추가·삭제된 def/class 는 전부 `_internal/` 아래이거나 중첩 함수 · `keycloak_sdk`·`keycloak_sdk.aio` 의 `__all__` 이 py-v1.0.6 과 바이트 동일.
- node 1.0.7 (patch) — 후보는 `sendableBearer`(token-provider.ts, 문서 주석만) 하나이고 index 가 다시 내보내지 않는다(package.json `exports` 는 "." 하나).
- go 1.2.5 (patch) — 후보는 전부 `_test.go` 이거나 비공개(`tieCtx`) · 비시험 파일의 exported 식별자 추가·삭제 0.
- dotnet 1.0.7 (patch) — `public override` 4 줄은 전부 `internal sealed` 클래스(ColdStart/ForcedRefetch ConfigurationManager) 안.
- php 1.5.1 (patch) — 새 `final class BearerToken` 은 php/src/Internal 아래이고 `@internal`.
- rust 1.1.5 (patch) — rust/src 의 `pub` 항목 추가·삭제 0.
- ruby 1.4.1 (patch) — 반영 워커(대조: 1.3.2→1.4.0 은 알려진 추가 둘 · 1.4.0→1.4.0 은 0). 1.4.0→main 에 `JwksStore::JWKS_MAX_AGE_SECONDS` 하나가 보였고 #914 가 private_constant 로 만든 뒤 1.4.0→main(d7d353a) = 0.
- 사실만 받은 독립 Grok 레그가 같은 아홉 번호에 닿았다(java 는 바로잡은 사실로 다시 물어 PATCH). Grok 의 이견 — 동작 변경(JWKS 300 초 fail-closed · 응답 전체 기한 · python 바이트 예산 · Bearer 문법)은 patch 가 아니어야 한다 — 은 DEPLOY §4 step 0(게시된 API 표면 기준 SemVer)과 선례(여섯째 물결이 새 거부 — 256,000 B 토큰 상한 — 를 여섯 레인에서 patch 로 냈고, minor 는 공개 상수가 늘어난 PHP 1.5.0 뿐)로 patch 유지. Grok 이 든 구체 시나리오(`=` 패딩 거부)는 소유자가 반증했다 — ruby bearer_grammar_spec `pad: sends "abc=="`(84 examples, 0 failures), 같은 "pad" 행이 go·node·dotnet·kotlin·php·java 표에도 있다.
다시 확인(이 커밋 — 태그 이후 매니페스트 diff, 끝점 3ca15f3): pyproject.toml(py-v1.0.6) · package.json(node-v1.0.6) · go.mod
(go/v1.2.4) · Cargo.toml(rust-v1.1.4) · keycloak-sdk.gemspec(ruby-v1.4.0) · kotlin/build.gradle.kts(kotlin-v1.0.7) 0 줄. csproj
(dotnet-v1.0.6) 3 줄 — 기준선 1.0.5 → 1.0.6(여섯째 물결 step 9) · DependencyInjection.Abstractions 9.0.21 → 9.0.22 · SourceLink
10.0.402 → 10.0.403(빌드 전용, 둘 다 dependabot #906). java pom 둘(v1.2.3) 4 줄 — japicmp.baseline 1.2.2 → 1.2.3(step 9) ·
surefire·failsafe 3.6.0 → 3.6.1 · jacoco 0.8.15 → 0.8.16(빌드 플러그인). php/composer.json(php-v1.5.0) — `ext-curl` · guzzle
`^7.15.2` · psr7 `^2.12.3`(#884 — PHP Changed 항목이 적는 것). node lock 의 런타임 셋은 node-v1.0.6 과 같다(admin-client
26.8.1 · openid-client 6.8.8 · jose 6.2.13).

바꾼 것:
- SSOT: scripts/lib/deploy-facts.sh — df_published_version 아홉 값(df_api_baseline 그대로).
- 매니페스트: python/pyproject.toml · node/package.json · node/package-lock.json(루트 두 줄, 손으로) · rust/Cargo.toml ·
  rust/Cargo.lock(`keycloak-sdk` 항목) · ruby/lib/keycloak_sdk/version.rb · ruby/Gemfile.lock(PATH 한 줄) · kotlin/build.gradle.kts ·
  dotnet csproj `<Version>` 1.0.6 → 1.0.7(PackageValidationBaselineVersion 1.0.6 그대로). go·php·java 는 편집 없음(태그가 SSOT ·
  php/composer.json 에 version 키 없음 · java POM 은 1.0.0-SNAPSHOT).
- 하네스: harness/apps/kotlin/build.gradle.kts 1.0.8(주석 + 선언) · harness/apps/rust/Cargo.lock `keycloak-sdk` 1.1.5.
  check-versions 가 그 밖에 요구한 자리 0.
- 아홉 <lang>/README.md(레지스트리 랜딩): 새 배너(버전 · 설치 해석 · 이번 릴리스 요약). 요약은 그 언어의 [Unreleased] 항목(태그가
  그 언어를 이름한 것 — 여러 언어 · `(전 언어)` 항목 포함)에서만 옮겼고 항목이 단 ⚠️ 행동 변화는 전부 옮겼다(Bearer 규칙과 그 언어의
  끝 개행·그랜트 거부 갈래 · JWKS 300 초 · 기한 Changed · ruby rack-oauth2 · php ext-curl · python 바이트 예산 · node·python
  메시지). 배너 숫자는 의존 핀을 빼고 전부 그 언어 항목 안에 있다(banner_numbers.py — 내용부만, 상투 머리·꼬리 · 버전 · .NET 의
  DependencyInjection.Abstractions 핀 제외 → 아홉 모두 미추적 0, 대조군: 4,321 을 넣은 사본 → UNTRACED 1). 지금 배너는 「`<old>`
  was a … release」 문단으로 내렸다 — node 는 틀(#812 · #855)대로 과거완료/「Since then」, 나머지 여덟은 머리만 바꾸고 새 배너로
  옮긴 상투 문장만 뺐다(demote 토큰 diff: php·ruby·java·rust·kotlin·dotnet 비동일 구간 2 · go·python 3 · node 22 = 시제만). 설치
  펜스/핀: java 둘 · kotlin · go · node. 같은 부류 자리(가드 밖): go Requirements 「for the published」 · go Install 「The go/vX tag is
  published」 · kotlin Requirements 「published」 · 「The published … reuses」 · node 「A bare install resolves」. 아홉 README 의
  한글 0 · 배너의 "not yet" 0.
  - PHP(#915 의 측정 — Composer 2.10.3 · PHP 8.3.36, ext-curl 대역 `ext-kcsdkprobe` 를 요구하는 판으로): 배너 머리 「where `ext-curl`
    is enabled, `composer require …` resolves `1.5.1`」 · ⚠️ 문장은 셋으로 — 제약 없는 require 는 경고만 내고 `1.5.0` 을 설치(종료
    코드 0), `1.5.1` 을 지정한 제약은 거부(`Your requirements could not be resolved …`), `1.5.1` 이 든 lock 의 `composer install` 도
    거부(`Your lock file does not contain a compatible set of packages …`). 경고는 측정이 대역 패키지라 글자로 인용하지 않고
    풀어 썼다. Requirements 의 ext-curl 줄에 「without the extension a bare `composer require xzawed/keycloak-sdk` stays on `1.5.0`」.
  - .NET: 1.0.5 선례(내린 문단의 「The package now depends on `Microsoft.Extensions.DependencyInjection.Abstractions` 9.0.21 or later
    (it was 9.0.20).」)대로 「… 9.0.22 or later (it was 9.0.21).」 — csproj 는 dotnet-v1.0.6 에서 9.0.21, 이 트리에서 9.0.22(#906).
- 함대 문장(일곱 갈래, 번호 내림차순 — 순서 그대로): README.md · README.ko.md · SECURITY.md · getting-started. 가드가 요구한 그
  밖의 자리: CLAUDE.md 현재 상태 표 · DEPLOY.md `- Install:` 셋 · compatibility.md 아홉 행 · language-support 매트릭스 ·
  getting-started 설치 헤딩 아홉과 `### 3)` 펜스 · README 영·한 Install 펜스(go · java · kotlin). 가드 밖 같은 부류 자리:
  compatibility 주석의 `go/v1.2.5` · getting-started 의 「is live」 여덟 · 「resolves」 넷 · java BOM 좌표 · go 「is published」 ·
  php 「serve `v1.5.1` today」 · kotlin 로컬 좌표 셋(`### 2)` 의 jar 이름 · mavenLocal 선언 · `~/.m2` 경로 — 매니페스트가 1.0.8) ·
  php·ruby 「a minor release」 → 「a patch release」 · php 「now resolves `1.5.1` where `ext-curl` is enabled (else `1.5.0`)」.
  getting-started 는 doc-budget 43,553 B 에 여유가 0 이라 그 한정(+43 B)을 같은 문단의 정보 없는 말 둘로 맞바꿨다 —
  「How publishing works here: 」(27 B) · 「, and it never could」(20 B). 원문 46,788 → 46,784 B · 래칫 인상 0. 나머지 함대 문서
  일곱의 바이트 크기 변화 0.
- CHANGELOG: [Unreleased] 의 36 항목(Changed 12 · Fixed 6 · Security 18)을 일곱 절로 — 여러 언어를 이름한 항목은 절 제목의 언어
  목록이 항목 태그와 겹치는 절마다 바이트 그대로 되풀이했다(선례: changelog/2026-09.md 의 2026-09-26 첫 물결 머리말 「여러 언어를
  이름한 항목은 해당하는 절마다 그대로 되풀이했습니다」) — `(전 언어)` 둘은 일곱 절 모두에. 60 줄(36 개): [1.5.1] (PHP) 6 ·
  [1.4.1] (Ruby) 9 · [1.2.5] (Go) 6 · [1.2.4] (Java) 7 · [1.1.5] (Rust) 6 · [1.0.8] (Kotlin) 7 · [1.0.7] (Python · .NET · Node) 19.
  절 안은 기준의 ### 범주 순서와 항목 순서 그대로. 첫 절 머리말(문단 넷: 무엇이 실리는가 · 번호와 왜 모두 patch 인가 · 되풀이 규칙과
  그 선례, 되풀이한 항목 안 「아래」·「위」의 뜻 · 같은 번호의 앞 절 셋 `[1.4.1] - 2026-10-23 (PHP)` · `[1.2.4] - 2026-10-27 (Go)` ·
  `[1.0.7] - 2026-10-27 (Kotlin)` 은 모두 보관본 2026-10, 앞 절이 없는 넷 · df_published_version 포인터), 나머지 여섯에 한 줄 머리
  ([1.0.7] 에는 .NET 키 회전 항목의 「last-known-good 은 그대로」 — 05fe878 가 먼저, 그것을 끈 .NET Security 항목은 c7b0eb4 로
  뒤에 착지). 되풀이로 Python 「위 줄의 본문 기한」은 [1.0.7] 안에서 다시 바로 위 줄을 가리킨다.
  PM 의 첫 지시는 선례를 「첫 교차 절」로 잘못 적었다 — 실제 선례(2026-09-26)는 되풀이이고, 그 규칙이 66179cf 을 46 = 46 으로 재현한다.
  (구현자 쪽 재생: check_move2.py wave1 — 66179cf^ 의 [Unreleased] 37 항목 → 기대 배치 46 · 66179cf 의 세 절 실제 46 · 배치 어긋남 0.)
  - 회전: 되풀이로 루트가 76,866 B(> 65,536)라 `node scripts/changelog-rotate.mjs --dry-run` → 계획 「76866B → 39227B — 날짜 1개 ·
    절 7개: 2026-10-27」 → 실행 같은 줄 → 다시 실행 「39227B ≤ 65536B — 옮길 것이 없다」. 옮긴 것: 2026-10-27 묶음 일곱 절 —
    [1.4.0] (Ruby) · [1.2.4] (Go) · [1.2.3] (Java) · [1.1.4] (Rust) · [1.0.7] (Kotlin) · [1.0.6] (Python · .NET · Node) · [1.5.0] (PHP),
    37,639 B → changelog/2026-10.md(126,322 → 163,961 B · 절 20 → 27). 옮긴 꼬리에 상대 링크가 없어 「한 단계 위」로 고친 링크 0.
    2026-08 · 2026-09 보관본은 바이트 그대로. 루트는 이제 [Unreleased] 와 2026-10-31 일곱 절뿐이다.
  - 옮기기: move2.mjs — Edit 툴을 거치지 않고 기준 blob(3ca15f3)을 Buffer 줄 단위로 복사한다. 절 머리 글은 sections2.json.
  - 검사(독립 — Python, 바이너리 I/O, sections*.json 도 옮기기·회전 코드도 읽지 않고 기대 제목 일곱을 스스로 고정): check_move2.py
    wave7 — LF · UTF-8 · 루트 머리(1,476 B) 바이트 동일 · [Unreleased] 내용 줄 39 → 0 · 일곱 제목 뒤 루트에 다른 절 없음 · 항목마다
    겹치는 절 각각에 바이트 그대로 정확히 1 회 · 겹치지 않는 절에 0 회 · 같은 ### 범주 · (절, 범주) 안 상대 순서 · 절 안 범주 순서 ·
    덧붙은 항목 0 · 2026-10 보관본 = 옛 머리 + 옛 루트 꼬리(상대 링크 한 단계 위) + 옛 본문 → RESULT: OK(60 줄 · 36 개).
  - 음성 대조: 작업본에서 Go 절의 `(전 언어)` JWKS 줄 한 바이트(300 → 301) → MISMATCH(3, exit 1) → 복원(cmp 동일) → OK. 사본
    여덟(Go 절에서 되풀이 하나 빼기 · Go 항목을 Rust 절에 · 범주 안 순서 · 범주 바꿈 · 보관본 한 바이트 · 루트 머리 한 바이트 · 옮긴
    절을 루트에도 남김 · [Unreleased] 에 항목 남김) — 여덟 모두 MISMATCH(exit 1). check-docs 8d 가 보존(main 대비)을 다시 봤다(아래).

하지 않은 것: df_api_baseline 과 기준선 사본(step 9) · .github/release-request.json · 태그 · push · PR · scripts/test/fixtures/** ·
doc-budget 인상.

틀과 다른 것 · PM 판단이 필요한 것:
- PHP 1.5.0 배너를 지시대로 내렸다(틀 57a112c 와 같다). PHP 1.5.0 범프(8c7ba42)는 1.4.1 배너를 내리지 않고 갈아치웠다.
- 배너에서 항목을 좁힌 곳: 응답 기한의 「디스커버리」는 .NET 배너에만(소스에서 well-known 을 받는 것은 dotnet JwtValidator.cs 하나) ·
  JVM 배너는 33.6 초만(「1 초 설정에 7 초」는 JVM 값으로 적히지 않았다) · .NET 배너의 「그대로」 목록에서 last-known-good 을 뺐다(위
  착지 순서) · node 배너는 옛 한글 메시지를 인용하지 않고 「which was in Korean」으로 썼다(README 한글 0 유지). 「No public API
  changed」는 아홉 배너 모두 — PM 판정 근거가 출처다.

함정: core.autocrlf=true(시스템 · 전역)라 eol 속성 없는 파일 아홉(csproj · build.gradle.kts 둘 · Cargo.lock 둘 · Cargo.toml ·
package.json · package-lock.json · pyproject.toml)이 체크아웃마다 작업본 CRLF 가 된다 — 처음 체크아웃에서도, 3ca15f3 로 rebase 한
뒤에도 그랬다. 두 번 다 바이트 단위로 CRLF → LF(crlf_to_lf.py)로 돌렸고 그 아홉의 diff 는 바이트 동일했다. rebase 의 CHANGELOG 충돌은
main 의 것을 받고 새 기준 blob 에서 옮기기를 다시 돌렸다. node lock 은 `--package-lock-only` 를 쓰지 않고 루트 두 줄만 Edit 로.
Docker 마운트는 MSYS_NO_PATHCONV=1 이 있어야 `-w /w` 가 `W:/` 로 바뀌지 않았다. python -c 에 한글 리터럴을 넣으면 명령줄이 cp949 로
깨진다 — 대조 스크립트의 앵커는 바이트/ASCII 로 바꿨다.

    MSYS_NO_PATHCONV=1 docker run --rm -v <wtbump>:/w -w /w debian:bookworm sh -c 'LANG= LC_ALL= dash scripts/test/test-publication-claims.sh'
        손대지 않은 트리 241 passed, 0 failed → SSOT 만 옮긴 트리 172 passed, 69 failed → 이 트리(3ca15f3 위) 241 passed, 0 failed
    node scripts/check-docs.mjs . --strict --min-facts=78 --min-anchors=26 --min-anchor-links=24 --min-blob-refs=5 --min-count-anchors=4
        → 한정을 처음 넣었을 때 exit 1(getting-started 적재 43658B > doc-budget 43553B) → 맞바꾼 뒤 exit 0 · 78 facts · 26 anchors ·
        31 anchor links · 66 self-blob targets · 12 archive blob refs (고유 9) · 8d 대조: Go 머리 문단에 901 B → 「항목 1082B > 1024B」
    node scripts/check-versions.mjs exit 0(경고 1건 — 기저 버전 갈림, 차단 안 함) · cd rust && cargo metadata --locked --offline exit 0
    sh scripts/test/test-changelog-ledger.sh 68/0 · test-deploy-facts 184/0 · test-check-versions 99/0 · test-readme-mirror 7/0 ·
    test-readme-badges 24/0 · test-security-defaults 487/0 · git diff --check 0 · 바뀐 31 파일 git ls-files --eol 전부 i/lf w/lf
    「가드 밖」 판정 확인(이 트리의 추적 파일 사본): 가드 밖이라 적은 24 자리를 전부 되돌려도 publication-claims 241/0 — 대조로 그
    사본의 python 설치 헤딩 하나를 더 되돌리면 240/1. 사본은 지웠다.
    test-changelog-landing.sh(커밋 뒤, merge-base 3ca15f3) 15/0 — 면제 표식 1 건, 게시 소스 1 개(ruby/lib/keycloak_sdk/version.rb)

[no-changelog: 버전 범프 — [Unreleased] 를 일곱 절로 옮겼다]


Claude-Session: https://claude.ai/code/session_01R4jYS4tpj7usahf8MK5dXo

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant