프로젝트

일반

사용자정보

Actions

이슈 #452

진행중
HP HP

이슈 #448: Phase 0.8 — dKernel Agent Identity Session

Phase 0.8-3 — Agent session entity + bootstrap RPC

이슈 #452: Phase 0.8-3 — Agent session entity + bootstrap RPC

Henry Park이(가) 약 5시간 전에 추가함.

상태:
해결
우선순위:
보통
담당자:
목표버전:
시작일:
2026/05/19
완료일:
진척도:

0%

추정시간:

설명

범위

gap-report.md §2, §7 Option C, §9. dKernel SSOT 자체 — lease / launch / policy / audit attribution 이 매달릴 갈고리.

작업

  • agent_sessions 테이블 — id, principal_type, principal_id, signer_kind (local|env|kms|hsm), signer_ref, current_namespace_type, current_namespace_id, current_cwd, created_at, expires_at, revoked_at
  • v21 마이그레이션
  • proto agent.proto + AgentSessionService — Create / Refresh / Revoke / GetInfo
  • AuthContext 에 SessionID 필드 추가. AuthInterceptor 가 X-Agent-Session 헤더 읽음
  • cmd/dshai startup 에서 session mint → 이후 모든 dshcore 호출이 session_id 헤더 포함
  • 0.8-2 의 command_audit.agent_session_id 채움
  • handler·store 테스트: cross-principal 격리, refresh idempotency, revoke 후 Unauthenticated
  • sdktest agent-conformance 명령 추가 (agent-bootstrap / agent-refresh / agent-revoke)
  • Web Console Test Client Section B 에 "Agent Session ID" 행 추가

DoD

  • agent_sessions 스키마 + v21 마이그레이션
  • AgentSessionService 4 RPC
  • AuthContext.SessionID + AuthInterceptor 연결
  • dshai startup 자동 bootstrap
  • command_audit 에 session_id 기록됨
  • sdktest + apismoke + Web Console Test Client 접점 안내

관련

  • depends on #450 (Signer) + #451 (audit columns)
  • precedes #448 자식 0.8-4 (DKernelTaskService 가 session 을 기본으로 사용)

HP Henry Park이(가) 5달 전에 변경 Actions #1

  • 상태을(를) 신규에서 해결(으)로 변경되었습니다.

0.8-3 Agent session entity + bootstrap RPC 구현 완료

commit:server2|a0ca19e

Phase 0.8 세 번째 슬라이스 마감. agent sessions 은 real persisted server-side entity — JWT 가 여전히 유일 permission factor, session 은 audit grouping/heartbeat/continuity/후속 revocation enforcement 의 authoritative source. 이번 슬라이스에서는 enforcement 안 켜짐 — 그대로 session_status 관측만.

구현

v22 마이그레이션

  • agent_sessions 테이블 (session_id UNIQUE, principal CHECK user|wallet|node, namespace, surface, client_name, signer_kind, metadata JSONB, created/expires/renewed/revoked/last_seen_at, command_count). 3 인덱스 (principal lookup, expires partial, session_id natural unique)
  • command_audit.session_status TEXT ALTER 추가 — audit row 에 서버가 관측한 session 상태 (active|expired|revoked|missing) 기록

Store layer (internal/store/agent_sessions.go)

  • InsertAgentSession (ses_<32-hex> 세션 ID mint)
  • GetAgentSession / RenewAgentSession (revoked row 방어) / RevokeAgentSession (멱등) / TouchAgentSession (per-RPC heartbeat) / ClassifyAgentSession (→ status 열)

4 RPC: Create/Renew/Revoke/GetInfo (AgentSessionService)

  • Create: principal_* 는 server-derived (클라이언트가 못 보냄). Surface/client_name/signer_kind/namespace hint 명시
  • Renew: revoked 면 FailedPrecondition / missing 면 NotFound — distinct codes 로 클라이언트 대응
  • Revoke: idempotent
  • GetInfo: full row + computed status
  • cross-principal Renew/Revoke/GetInfo = PermissionDenied

Audit attribution — 4 번째 헤더

  • X-Alphea-Agent-Session-Id 가 기존 3 헤더와 함께 stamp
  • server CommandAuditInterceptor 가 200ms detached ctx 로 GetAgentSession lookup → ClassifyAgentSession → command_audit.session_status
  • active row 면 비동기 TouchAgentSession (last_seen_at + command_count++) fan-out. expired/revoked/missing 은 touch 안 함 (dead row 을 bump 하면 lifecycle 왬곡)
  • INSERT + Touch 동일 off-request goroutine — RPC 응답 latency 에 제로

dshcore.Client (클라이언트)

  • mutex-guarded sessionState + SetSessionID/SessionID — auto-renew daemon 이 hot-swap 가능
  • AgentService() 도 표면에 노출 (같은 auth+audit transport)
  • HeaderAlpheaAgentSessionID 추가. TestAuditHeaderConstantsMatchServer 가 4-way sync 검증

cmd/dshai bootstrap

  • startup 흐름: wallet token 확보 → dshcore.NewClient(surface="dshai") → bootstrapAgentSession → SetSessionID + atomic state file write
  • resume 규칙: state.AgentSessionID 가 expiresAt > now+60s 면 재사용 (audit continuity 보존)
  • bootstrap 실패 = hard fail (no --no-session escape hatch)
  • --auto-renew daemon 이 wallet refresh 이후 Renew 시도 → NotFound/FailedPrecondition 면 Create fallback + state 갱신

테스트

internal/server/command_audit_test.go:

  • TestInterceptorChainRecordsActiveSession — active row → audit + Touch
  • TestInterceptorChainRecordsExpiredSession — expired row → audit ∀ status=expired + no touch
  • TestInterceptorChainRecordsMissingSession — row 없음 → audit ∀ status=missing + no touch
  • fakeAuditWriter 가 새 GetAgentSession + TouchAgentSession 만족 + putSession injector

internal/dshcore/audit_header_test.go:

  • 4-way header sync 추가
  • TestAuditMetadataInterceptorOmitsEmptySessionID — 세션 ID 부재 시 헤더 속괌 (server backfill 경로 활용)

검증

go test  ./sdk ./internal/dshcore ./internal/server \
         ./internal/store ./internal/handler \
         ./cmd/dshai ./cmd/dsh ./cmd/sdktest      — all clean
go vet   ./...                                    — clean
go build ./...                                    — clean

Permission boundary

JWT = permission gate. agent_session = durable identity row + audit continuity. 두 테이블, 두 책임. 권한 결정에 session 이 근접하지 않음.

이 슬라이스가 포함하지 않는 항목

  • DKernelTaskService skeleton (#453)
  • 문서 정리·patent-mapping (#454)
  • session enforcement (revoked/expired RPC 거부) — dKernel 슬라이스에서

다음 진입: #453 (0.8-4 DKernelTaskService skeleton).

HP Henry Park이(가) 5달 전에 변경 Actions #2

0.8-3 review findings 먹음

commit:server2|a3cb45c

첫 컷 (a0ca19e) 후 리뷰에서 나온 네 건 (Medium 3 + Low 1) 모두 같은 슬라이스에 접어 넣음.

1) Medium — bootstrap resume 가 server-validated. 첫 컷은 local expiry 만 확인 → stale/revoked/cross-principal session_id 를 프로세스 전체 동안 stamp 가능 (--auto-renew 꺼져있으면 영구). startup bootstrap 이 hard requirement 이니 resume 도 엄격 한와. Fix: tryResumeAgentSession 가 AgentSessionService.GetInfo 호출 (a) row 존재 (b) current bearer principal 소유 (cross-principal 는 server 가 PermissionDenied) (c) status="active". 다른 모든 케이스 (missing/revoked/expired/transport error) → fresh Create

2) Medium — handler input validation. 두 경계 leak. (a) metadata JSONB invalid JSON 이 CodeInternal 로 떨어짐 → 이제 handler 에서 json.Unmarshal probe → CodeInvalidArgument. (b) namespace lowercase 만 되고 scheme+address shape 검증 안 함 → normaliseAgentSessionNamespace 헬퍼 추가, wallet|token + lowercase 0x + 40 hex 규칙 (audit extractor 와 동일). 둘 다 empty 면 "no hint" valid shape 로 유지

3) Medium — audit GetAgentSession 이 sync 이어서 response path 차지 (200ms latency 추가). "session bookkeeping never blocks RPC" 약속과 모순. Fix: GetAgentSession + ClassifyAgentSession 도 off-request goroutine 으로 이동. session_status 가 INSERT 와 비동기 계산되지만 Go memory model 이 구조체 ref 의 happens-before 보증. chain integration test 가 여전히 fake writer 의 channel 에서 entry 받을 때 status 채워져있는지 검증하므로 계약은 observable

4) Low — DB-backed lifecycle tests. 二 신규 파일:

  • internal/store/agent_sessions_test.go (TEST_DATABASE_URL):
    • TestAgentSessionLifecycle — create → get → touch → renew → revoke → revoked-renew error → revoke 멱등
    • TestAgentSessionMissingErrors — Get/Renew/Revoke 의 각 missing 케이스가 ErrAgentSessionNotFound 를 surfacing
    • TestClassifyAgentSession — nil/revoked/expired/active 4 sub-case (pure function, DB 불필요)
  • internal/handler/agent_test.go (DB-free, nil DB 사용):
    • Unauthenticated / empty surface / invalid metadata JSON / invalid namespace_type / invalid namespace_id 모두 InvalidArgument
    • TestNormaliseAgentSessionNamespace 헬퍼 validator table (소문자/대문자, scheme rejection, short id, non-hex, missing 0x)

검증

go test  ./sdk ./internal/dshcore ./internal/server \
         ./internal/store ./internal/handler \
         ./cmd/dshai ./cmd/dsh ./cmd/sdktest      — all clean
go vet   ./...                                    — clean
go build ./...                                    — clean

DB-backed store tests (TEST_DATABASE_URL 의존) 는 환경이 없을 때 skip; dev Postgres (alphea_test DB) 에서는 실제 동작 검증 가능.

decision-log 의 2026-05-20 follow-up entry 에 네 건의 "왜 그 첨 구현에서 이 올가미가 나왔는지" 명시.

HP Henry Park이(가) 5달 전에 변경 Actions #3

0.8-3 second-round review Lows 먹음

commit:server2|16036df

이전 follow-up (a3cb45c) 의 2차 리뷰에서 나온 두 Low 모두 같은 슬라이스에 접어 넣음.

1) Low — cross-principal / FailedPrecondition / NotFound 커버리지가 handler test 에 있어야. store.Renew/Revoke/GetAgentSession 은 principal-blind (session_id 만 권수); principal 비교는 handler 에서 일어남. 이전 cut 이 cross-principal guard test 를 store 테스트에 놓아 regression 신호가 머될 수 없었다. Fix: DB-backed handler test 6종 신규 (TEST_DATABASE_URL 의존):

  • TestAgentSessionCreateFreezesPrincipal — bearer 에서 파생됨 검증
  • TestAgentSessionRenewRejectsCrossPrincipal / RevokeRejectsCrossPrincipal / GetInfoRejectsCrossPrincipal
  • TestAgentSessionRenewOnRevokedMapsToFailedPrecondition
  • TestAgentSessionMissingMapsToNotFound (Get/Renew/Revoke 3 sub-case)

node_test.go 의 openTestDB(t) 재사용. 파일 주석도 DB-free vs DB-backed 분할 정확하게 재작성.

2) Low — metadata validator helper 분리. 이전 cut 의 TestAgentSessionCreateAcceptsEmptyMetadata 가 nil-DB panic 을 defer recover() 로 삼키던 구조 — 테스트가 validator 의 올/그름과 무관하게 세이브. Fix: validateAgentSessionMetadata([]byte) error 를 handler body 에서 추출. handler 는 그저 호출 후 에러를 CodeInvalidArgument 로 lift. TestValidateAgentSessionMetadata 가 이 helper 를 직접 10개 케이스로 (nil, empty slice, valid object/array/primitive/null, plain text, truncated, unbalanced) 검증 — DB X, recover X.

부수 fix (dev-DB run 출처): TestAgentSessionLifecycle 가 req.Metadata vs fetched.Metadata byte-for-byte 비교 했는데 Postgres JSONB 가 round-trip 시 공백 정규화 → {"model":"test"} → {"model": "test"}. 이제 둘 다 unmarshal 후 reflect.DeepEqual — 계약은 "same JSON document" 이지 "same byte sequence" 가 아님.

검증 (두 구성)

# TEST_DATABASE_URL unset:
go test  ./sdk ./internal/dshcore ./internal/server \
         ./internal/store ./internal/handler \
         ./cmd/dshai ./cmd/dsh ./cmd/sdktest      — all clean (DB-backed skip)
go vet   ./...                                    — clean
go build ./...                                    — clean

# TEST_DATABASE_URL=postgres://…/alphea_test:
v21/v22 migrations 자동 적용. 6개 DB-backed handler test +
lifecycle/missing store test 모두 PASS.

decision-log 에 2026-05-20 follow-up entry 추가 — 두 finding 의 root cause 명시.

Actions

내보내기 PDF Atom