Skip to content

feat: LLM API 키를 두 번째 리소스 종류로 추가 - #49

Merged
yessjun merged 20 commits into
mainfrom
feat/llm-key-resource-type
Aug 11, 2026
Merged

feat: LLM API 키를 두 번째 리소스 종류로 추가#49
yessjun merged 20 commits into
mainfrom
feat/llm-key-resource-type

Conversation

@yessjun

@yessjun yessjun commented Aug 11, 2026

Copy link
Copy Markdown
Member

📝 작업내용

  • LLM API 키를 두 번째 리소스 종류로 추가
    • 신청 처리기와 리소스 어댑터, 발급과 회전, 폐기, 수정
    • 승인은 키를 만들고 소유자가 직접 발급, 평문은 그때 한 번만 표시
    • 발급 전 상태는 게이트웨이 문서에 실리지 않음, 비밀 없는 키는 아무것도 인증하지 못함
  • 무필터 리소스 목록을 종류 병합으로 전환
    • 종전 구현은 어댑터가 둘이 되는 순간 500, 기동 실패가 아니라 요청별 실패
    • 어댑터 인터페이스에서 Pageable 제거, 순서를 속성 이름이 아니라 의미로 규정
    • UUID를 비교하지 않음, Java는 부호 있는 long 둘로 PostgreSQL은 부호 없는 바이트로 정렬
  • 승인 부여 불변식을 종류 무관으로 일반화, 모르는 종류는 통과가 아니라 거부
  • 게이트웨이 연동 3경로, 전용 보안 체인과 하위 경로별 빈도 제한과 본문 상한
    • 세대는 시퀀스가 아니라 단일 행을 쓰기보다 먼저 잠금
    • 응답을 sealed 쌍으로, 두 항목 중 하나만 싣는 것을 표현 불가능하게
  • 모델 카탈로그 테이블 신설, 행은 운영 스크립트 소유
  • API 명세 v0.40.0, 권한 매트릭스 157행, 재인증 2건 등재

⭐️ 검증

  • 검증 스크립트 통과, 테스트 전건과 생성 명세 대조, 수기 마스터 대조
  • 스코핑 스위트 45개 신설, 8개 op를 다섯 처지로 순회
  • dev 배포 후 관통 확인, 신청에서 승인 발급 동기화 실호출 사용량 폐기까지

💬 리뷰 포인트

  • 발급은 OWNER 부여, 폐기는 상시권과 관리자. 비밀을 만드는 것은 내용 접근이고 가져가는 것은 아님

yessjun added 20 commits August 11, 2026 16:00
The approver decides that somebody may have a key; only its owner may ever
see the plaintext, and the approver is not in the room when they do. So
approval creates the key, its access list and its period, and the owner mints
the secret themselves - once. A key between the two is PENDING with no hash,
and the gateway is not told about it at all: a key that authenticates nothing
has no state worth publishing.

Carries the token hash's char(64) mapping with it. Hibernate validates the
schema at startup, so without it every application context refused to build -
which no compile and no single-class run catches.
Promote the relay filter's chunked-body cap wrapper to common.web so
the LLM gateway chain can reuse it. Behaviour unchanged.
The dedicated /internal/llm chain sits ahead of the broad /internal
catch-all, which moves one order slot down with behaviour unchanged.
The unchanged poll answers the bare generation without building the
document, a generation reported above ours raises the counter instead
of being discarded, per-event usage problems are never a 4xx, and the
bodies endpoint accepts and deliberately stores nothing.
The VM list's grant-to-visibility reduction is the platform's access rule,
not the VM's; lifted so a second resource type reuses it instead of
copying it. VM behavior is unchanged.
The list and detail a user sees for their keys, plus the key's
access-list endpoints as a pass-through into the shared grant service.
Visibility follows the platform rule: only a grant opens a row, a
member without one gets a restricted row that never carries the token
prefix, and a non-member is answered with the 404 existence mask. The
token hash appears in no response, asserted against whole bodies.

The published spec snapshot is regenerated in the contract round that
versions this surface; until then ContractDriftTest's snapshot check is
the one expected failure.
The matrix marks the key's operations resource-scoped, which the
annotation-reading matrix test cannot verify at runtime. Drive all
eight operations across the five standings, and pin the deliberate
asymmetry between issuing (an OWNER grant, never standing rights)
and revoking (a standing right of workspace owners and admins),
plus rotation, the revoked-key conflict and revoke idempotence.
Revoking is a standing right in the access model - taking a resource away,
which a workspace owner holds without a grant - and the code required an OWNER
grant for it. During a leaked-key incident that left the only people able to
stop it as the ones already on its access list, and a workspace owner whose key
owner had left would have had to grant themselves content access first: a
break-glass record for taking something away. ORG_ADMIN (own org) and SYS_ADMIN
now reach it too, matching VM deletion.

Minting stays on the OWNER grant. Handing somebody a working credential is
content access, and standing rights deliberately do not include that - the same
line the VM draws at reading a password.

Also here, because both are in the same file: issue re-reads the status after
taking the generation lock, since the check before it ran against a snapshot a
concurrent revoke could have invalidated - the full-column update would then
have written ACTIVE and a null revoked_at back over it. And update() no longer
folds purpose into rename, which erased a purpose whenever somebody renamed
without resending it, contradicting the schema's own '생략한 항목은 그대로 둡니다'.
@yessjun
yessjun merged commit 1d64015 into main Aug 11, 2026
1 check passed
@yessjun
yessjun deleted the feat/llm-key-resource-type branch August 11, 2026 14:31
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