Merge branch 'main' of https://git.toki-labs.com/toki/iop
This commit is contained in:
commit
1550e8cacc
5 changed files with 191 additions and 15 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -41,3 +41,6 @@ packages/**/pubspec.lock
|
|||
!agent-task/**/*.log
|
||||
agent-roadmap/current.md
|
||||
# END Agent-Ops managed gitignore
|
||||
|
||||
# Local dev-corp operator tokens (never tracked)
|
||||
/token/
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@
|
|||
## 스킬 라우팅
|
||||
|
||||
- UI 없는 사용자 CRUD, OpenAI-compatible 사용자/principal 추가·조회·수정·비활성화·삭제, principal token 운영 CRUD: `agent-ops/skills/project/iop-user-crud-ops/SKILL.md`
|
||||
- OpenAI-compatible usage token 발급, principal_ref token 등록, principal alias 매핑, raw IOP token 1회 전달: `agent-ops/skills/project/openai-usage-token-issue/SKILL.md`
|
||||
- Confluence 문서 작성, 컨플 문서 생성·갱신·검토, Lab2 문서 작성, lgucorp 위키 업데이트: `agent-ops/skills/project/lgucorp-confluence-docs/SKILL.md`
|
||||
- OpenAI-compatible 사용자 token 발급·추가, dev-corp 사용자 추가와 token 발급, principal_ref token 등록, principal alias 매핑, raw IOP token 1회 전달: `agent-ops/skills/project/openai-usage-token-issue/SKILL.md`
|
||||
- dev-corp 배포, dev-corp runtime 배포, 회사망 mac-mini Edge/Node dev-corp 환경 배포, dev-corp provider pool 배포, dev-corp OpenAI-compatible capacity smoke 검증: `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md`
|
||||
- dev 배포, dev-runtime 배포, Edge/Node dev 환경 배포, provider pool 배포, OpenAI-compatible capacity smoke 검증: `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
|
||||
- 사용자 실행 파이프라인 검증, repo 내부 edge-node 진단, 메시지 2회 왕복, edge command 응답, 보조 E2E smoke, full-cycle 실제 구동, `scripts/dev/edge.sh`/`scripts/dev/node.sh` 진단 테스트: `agent-ops/skills/project/e2e-smoke/SKILL.md`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: dev-corp-runtime-deploy
|
||||
version: 1.0.10
|
||||
version: 1.0.12
|
||||
description: dev-corp 배포, public digitalplatform Edge와 내부 DGX/Mac Studio provider pool 배포 및 OpenAI-compatible capacity smoke 절차
|
||||
---
|
||||
|
||||
|
|
@ -50,6 +50,7 @@ dev-corp provider pool을 현재 dev-runtime과 같은 구조로 배포한다.
|
|||
- [ ] 현재 구현의 completion 검증 대상은 legacy `/v1/completions`가 아니라 `/v1/chat/completions`이다. `/v1/completions`는 route가 구현되어 있을 때만 별도 검증한다.
|
||||
- [ ] dev-corp current runtime은 OpenAI-compatible bearer token이 켜져 있다. smoke는 token 원문을 출력하지 말고 `OPENAI_API_KEY=$(cat build/dev-corp-runtime/.secrets/openai_api_key)` 또는 `--api-key-file`로 실행한다.
|
||||
- [ ] token, secret header, bootstrap token, private key 경로는 최종 보고에 원문으로 출력하지 않는다.
|
||||
- [ ] 원격 host의 보안 정책 때문에 `scp`/`rsync` 같은 파일 전송 프로토콜이 `Operation not permitted`로 막히면 권한·ACL·보안 정책을 완화하거나 재시도하지 않는다. 산출물과 config는 SSH 표준 입력 스트림으로 canonical target과 같은 디렉터리에 새 remote candidate 파일을 생성하는 절차를 사용한다.
|
||||
|
||||
## known/current runtime update notes
|
||||
|
||||
|
|
@ -98,7 +99,7 @@ dev-corp provider pool을 현재 dev-runtime과 같은 구조로 배포한다.
|
|||
|
||||
5. **dev-corp runtime rebuild**
|
||||
- 같은 source ref에서 dev-corp Control Plane binary, Edge binary, mac node binary, Linux ARM64 node binary를 다시 빌드한다.
|
||||
- mac-mini에서 직접 빌드할 수 없으면 같은 source ref 기준으로 로컬에서 빌드한 뒤 `rsync/scp`로 mac-mini runtime 경로에 배포한다.
|
||||
- mac-mini에서 직접 빌드할 수 없으면 같은 source ref 기준으로 로컬에서 빌드한 뒤 SSH 표준 입력 스트림으로 mac-mini canonical target과 같은 디렉터리의 새 candidate 경로에 생성한다. `scp`/`rsync` 전송 재시도나 권한 완화는 하지 않는다.
|
||||
- Windows AMD64 node binary는 dev-corp 기본 provider pool 대상이 아니다.
|
||||
- stale binary가 의심되거나 `config refresh` subcommand, admin port, version 출력이 맞지 않으면 clean sync부터 다시 시작한다.
|
||||
- 빌드 산출물 경로, timestamp, 크기, 실행 가능 여부를 기록한다.
|
||||
|
|
@ -110,6 +111,10 @@ dev-corp provider pool을 현재 dev-runtime과 같은 구조로 배포한다.
|
|||
7. **배포와 재시작**
|
||||
- Control Plane이 public host에서 운용되는 경우 `iop.ai.kr:18002/19004/19005` dev-corp port를 기준으로 확인한다. mac-mini local Control Plane은 dev-corp Edge source of truth가 아니다.
|
||||
- Edge는 public `iop.ai.kr` host에서 `18085/18086/18087`을 기준으로 재시작/확인한다. mac-mini runner runtime은 dev-corp Edge source of truth가 아니다.
|
||||
- **보안 전송 차단 시 SSH stdin candidate 표준**: `scp`/`rsync` 같은 파일 전송 프로토콜이 `Operation not permitted`로 차단되면 권한을 올리거나 전송을 재시도하지 않는다. canonical target과 같은 디렉터리에 timestamp 또는 고유 suffix를 붙인, 아직 존재하지 않는 candidate 경로를 먼저 정한다. 같은 filesystem에 두어야 이후 `mv` 전환이 가능하다.
|
||||
- source artifact는 `ssh <target> 'umask 077; tee /absolute/path/to/target.candidate >/dev/null' < /absolute/path/to/local-artifact`처럼 SSH stdin으로 candidate를 생성한다. config는 `<config-generator> | ssh <target> 'umask 077; tee /absolute/path/to/config.candidate >/dev/null'`처럼 생성 결과만 stream으로 보낸다. candidate path는 canonical target과 같은 디렉터리의 새 경로로 치환하며, token·config 원문을 command argument, 터미널 출력, command history, 배포 보고에 남기지 않는다.
|
||||
- cutover 전에 source와 remote candidate의 SHA-256과 byte length를 비교한다. macOS에서는 `shasum -a 256`, Linux에서는 `sha256sum`을 사용한다. candidate의 소유자와 mode도 확인하고, 실행 binary는 검증 후에만 `chmod 755`로 전환하며 secret-bearing config는 `0600`을 유지한다. 불일치 또는 preflight 실패 시 cutover하지 않는다.
|
||||
- 현재 실행 파일은 보존한 채 candidate의 `--help`와 필요한 config check를 먼저 통과시킨다. 전환은 같은 filesystem에서 canonical 파일을 timestamp backup으로 `mv`한 뒤 candidate를 canonical path로 `mv`한다. restart 또는 readiness 실패 시 backup을 canonical path로 다시 `mv`하여 즉시 rollback한다.
|
||||
- DGX Spark 01/02는 Linux ARM64 node binary를 mac-mini 경유로 배포하고 기존 node process를 재시작한다.
|
||||
- Mac Studio는 macOS node binary를 mac-mini 경유로 배포하고 기존 node process를 재시작한다.
|
||||
- provider runtime 옵션을 갱신하는 배포라면 DGX Spark 01은 `/home/digitalcommerce_dgx_spark_01/start_vllm_ornith35b_docker_8003.sh` 기준으로 Docker container `iop-vllm-ornith35b-fp8`를 재생성한다. 핵심 옵션은 `--max-model-len 262144`, `--gpu-memory-utilization 0.47`, `--max-num-seqs 4`, `--port 8000`, `--enable-prefix-caching`, `--enable-auto-tool-choice`, `--tool-call-parser qwen3_xml`, `--reasoning-parser qwen3`, `--trust-remote-code`, `--runner generate`이다.
|
||||
|
|
@ -174,6 +179,8 @@ dev-corp runtime 배포 결과
|
|||
- 사용자가 명시적으로 요청하지 않았는데 mac-mini runner/provider 관리 경로를 dev-corp Edge runtime, Node `edge_addr`, bootstrap 기본 URL, OpenAI-compatible base URL로 사용하지 않는다.
|
||||
- `git clean -fdx`를 기본 cleanup으로 사용하지 않는다.
|
||||
- 빌드 전 테스트 실패 후 배포를 계속하지 않는다.
|
||||
- `scp`/`rsync`가 보안 정책으로 차단됐을 때 권한·ACL·보안 도구를 변경하거나 `sudo`로 우회하지 않는다. 같은 filesystem의 새 SSH stdin candidate 생성, checksum 검증, `mv` cutover/rollback 절차를 사용한다.
|
||||
- `umask 077`으로 생성된 실행 binary를 `0600` 상태로 기동하지 않는다. checksum 검증 후 필요한 실행 권한만 부여한다.
|
||||
- DGX Spark 02 Ornith provider endpoint를 `192.168.2.4:8000`, `8001`, `8002`, `8004`로 잘못 사용하지 않는다. 현재 기본은 `192.168.2.4:8005`이다.
|
||||
- Mac Studio secondary `8005` endpoint를 별도 alias/capacity 결정 없이 기본 provider pool에 포함하지 않는다.
|
||||
- Mac Studio `8004` vLLM-MLX runtime 재시작 방식을 확인하지 않고 provider process를 임의 종료하지 않는다.
|
||||
|
|
|
|||
103
agent-ops/skills/project/lgucorp-confluence-docs/SKILL.md
Normal file
103
agent-ops/skills/project/lgucorp-confluence-docs/SKILL.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
name: lgucorp-confluence-docs
|
||||
version: 1.0.4
|
||||
description: lgucorp Confluence 문서를 작성, 갱신, 검토하고 사용자가 명시적으로 승인한 token metadata table을 지정된 draft/page에 동기화하는 운영 절차. Confluence 문서 작성, 컨플 문서 생성·수정·검토, Lab2 위키 업데이트, 승인된 token metadata table 동기화 요청에 사용한다.
|
||||
---
|
||||
|
||||
# lgucorp-confluence-docs
|
||||
|
||||
## 목적
|
||||
|
||||
`lgucorp.atlassian.net`의 Lab2 기본 폴더 아래에서 Confluence 문서를 안전하게 작성, 갱신, 검토한다. 기본 대상은 [Lab2 폴더](https://lgucorp.atlassian.net/wiki/spaces/Lab2/folder/650886407)이며, 사용자 요청이 없으면 이 폴더 밖에 문서를 만들거나 이동하지 않는다.
|
||||
|
||||
## 언제 호출할지
|
||||
|
||||
- Confluence 또는 컨플 문서를 Lab2에 새로 작성해야 할 때
|
||||
- Lab2 Confluence 문서를 수정하거나 최신 내용으로 갱신해야 할 때
|
||||
- 기존 Lab2 Confluence 문서의 내용, 구조, 링크, 상위 폴더를 검토해야 할 때
|
||||
|
||||
## 입력
|
||||
|
||||
- `operation`: `create`, `update`, `review`, `sync-token-metadata-table` 중 하나 (필수)
|
||||
- `title`: 새 문서 제목 또는 갱신·검토할 문서 제목 (create 필수)
|
||||
- `page_id` 또는 `page_url`: 갱신·검토 대상. 제목만으로 대상이 하나로 확정될 때는 생략 가능 (update/review 필수)
|
||||
- `content`: 작성·갱신할 최종 본문. Confluence storage 형식 또는 변환 가능한 Markdown/HTML (create/update 필수)
|
||||
- `target_folder_id`: 기본값 `650886407`. 사용자가 다른 위치를 명시할 때만 변경 (선택)
|
||||
- `atlassian_user`: HTTP Basic 사용자 식별자는 `leedongmyung@lguplus.co.kr`로 고정한다. Git 사용자 정보나 추정값으로 대체하지 않는다. (필수)
|
||||
- `secret_store_path`: `sync-token-metadata-table`의 ignored user-key source file. raw token은 추출·전달하지 않고 user key 집합 대조에만 사용한다. (선택)
|
||||
- `sync_target_path`: `sync-token-metadata-table`의 ignored target URL file. 이 파일은 user-approved exact target 하나만 가진다. (선택)
|
||||
|
||||
## 먼저 확인할 것
|
||||
|
||||
- [ ] repo root의 `token/.lgu-atlassian-token`이 존재하고, 내용은 한 개의 raw token뿐이며 mode가 `0600`인지 확인한다.
|
||||
- [ ] `git check-ignore -q token/.lgu-atlassian-token`이 성공하고 `git ls-files --error-unmatch token/.lgu-atlassian-token`이 실패하는지 확인한다.
|
||||
- [ ] API 호출은 `leedongmyung@lguplus.co.kr`와 token 파일을 사용한 HTTP Basic 인증으로 수행한다. 이 토큰에는 Bearer 인증을 사용하지 않는다.
|
||||
- [ ] `GET /wiki/rest/api/user/current`과 `GET /wiki/api/v2/folders/650886407`으로 인증과 기본 폴더 접근을 확인한다. folder 응답에서 실제 `spaceId`를 읽는다.
|
||||
- [ ] update/review는 `GET /wiki/api/v2/pages/{page-id}?body-format=storage`으로 대상 page id, 현재 title, version, parentId를 먼저 확인한다. 제목만으로 대상이 둘 이상이면 쓰기 작업을 중단하고 page id 또는 URL을 요청한다.
|
||||
- [ ] create는 `GET /wiki/api/v2/folders/650886407/direct-children`을 페이지네이션까지 확인해 같은 제목의 문서가 없는지 검사한다. 같은 제목이 있으면 중복 생성하지 않고 update 대상으로 전환할지 확인한다.
|
||||
- [ ] `sync-token-metadata-table`은 사용자가 지정한 local user-key source와 active IOP mapping metadata가 1:1로 대조되고, exact target이 명시된 경우에만 수행한다. source/target 모두 `0600`, ignored, untracked인지 확인한다.
|
||||
- [ ] target이 draft URL이면 `draftId`를 추출해 `GET /wiki/api/v2/pages/{draftId}?body-format=storage`으로 title, status, version, parentId, storage body를 먼저 읽는다. raw token과 storage body 원문은 출력하지 않는다.
|
||||
|
||||
## 실행 절차
|
||||
|
||||
1. **안전한 인증 준비**
|
||||
- token은 `token/.lgu-atlassian-token`에서만 읽고, shell trace를 켜지 않는다.
|
||||
- `atlassian_user`는 반드시 `leedongmyung@lguplus.co.kr`를 사용한다. `git config user.email`, Git 사용자명, 환경별 추정값으로 대체하지 않는다. 최종 보고에는 실제 이메일을 쓰지 않는다.
|
||||
- 모든 요청의 host는 `https://lgucorp.atlassian.net`, base path는 `/wiki`로 고정한다.
|
||||
- token 원문을 shell 인자, curl verbose 출력, 파일명, JSON payload, tracked 문서, 최종 보고에 남기지 않는다.
|
||||
|
||||
2. **대상 폴더와 문서 식별**
|
||||
- 기본 parent는 folder id `650886407`이며 URL은 `https://lgucorp.atlassian.net/wiki/spaces/Lab2/folder/650886407`이다.
|
||||
- folder v2 응답의 `spaceId`와 대상 page의 `parentId`를 사용해 Lab2 기본 폴더 범위를 확인한다.
|
||||
- 기본 폴더 밖의 page id, URL, 폴더 이동 요청은 사용자가 명시적으로 허용한 경우에만 처리한다.
|
||||
|
||||
3. **문서 작성 또는 갱신**
|
||||
- create는 `POST /wiki/api/v2/pages`로 `spaceId`, `status: current`, `title`, `parentId: 650886407`, `body.representation: storage`, `body.value`를 보낸다. JSON은 `jq -n` 또는 동등한 구조화 생성기로 만들고, 본문이 JSON escape 오류 없이 전달되는지 확인한다.
|
||||
- update는 현재 page의 `version.number`와 `status`를 읽은 뒤 `PUT /wiki/api/v2/pages/{page-id}`로 `id`, 현재 `status`, `title`, storage body, `version.number: 현재값 + 1`을 보낸다. 명시적 요청이 없으면 title과 parent를 변경하지 않는다.
|
||||
- 본문 갱신은 전체 storage body를 교체하므로, 동시 편집 또는 draft가 있으면 최신 본문을 다시 읽고 사용자 요청 내용과 충돌하는지 검토한 뒤에만 쓴다.
|
||||
- HTTP 401/403/404/409 또는 예상하지 않은 2xx 외 응답에서는 재시도 쓰기를 하지 않고, 상태 코드와 민감값 없는 오류 요약만 보고한다.
|
||||
- `sync-token-metadata-table`은 user key와 active mapping metadata를 `principal_ref` 기준으로 대조한 뒤 `사용자`, `principal alias`, `token ref`, `상태`, `동기화 시각`만 storage table로 stream 생성한다. target body가 비어 있으면 표 body 전체를 쓰고, 비어 있지 않은 body는 전용 동기화 heading이 있는 경우에만 해당 섹션을 교체한다.
|
||||
- draft target은 `PUT /wiki/api/v2/pages/{draftId}`에 동일 id, 기존 title, 현재 `status: draft`, storage body, `version.number: 현재값 + 1`을 보낸다. Basic 인증과 JSON body는 non-logging stream으로 전달하며 raw token을 shell 인자·payload에 넣지 않는다.
|
||||
|
||||
4. **문서 검토**
|
||||
- review는 읽기 전용으로 수행한다. title, status, version, parentId, storage body의 heading 구조, 빈 섹션, 깨진 내부 링크 후보, 중복 제목, 사용자 요청과의 차이를 확인한다.
|
||||
- 검토 결과는 수정 권고와 근거를 분리해 보고하며, review 요청만으로 페이지를 수정하지 않는다.
|
||||
|
||||
5. **작성 후 검증과 보고**
|
||||
- create/update 뒤 `GET /wiki/api/v2/pages/{page-id}?body-format=storage`으로 id, title, status, version, parentId가 기대값인지 확인한다.
|
||||
- create는 `parentId=650886407`과 Lab2의 `spaceId`를 확인하고, update는 의도하지 않은 title 또는 parent 변경이 없는지 확인한다.
|
||||
- 결과에는 operation, page id, title, version, parent folder, page URL, 검토 결과 또는 검증 상태만 기록한다.
|
||||
|
||||
## 실행 결과 검증
|
||||
|
||||
- [ ] token 파일은 mode `0600`, ignored, untracked 상태를 유지한다.
|
||||
- [ ] 기본 폴더 id `650886407`이 Lab2에서 조회되고 실제 `spaceId`를 확인했다.
|
||||
- [ ] create/update 결과 page의 id, title, status, version, parentId를 재조회해 확인했다.
|
||||
- [ ] review는 페이지 쓰기 요청 없이 끝났다.
|
||||
- [ ] `sync-token-metadata-table`은 content id·status·version을 재조회하고, table의 행 수·사용자/alias/ref/status metadata가 active mapping과 일치하는지 내부 비교한다. 비교 결과만 기록하고 raw cell 값은 출력하지 않는다.
|
||||
- [ ] raw token, Basic 인증 header, 전체 응답 본문이 tracked 파일이나 최종 보고에 남지 않았다.
|
||||
- 검증 실패 시: 생성·갱신 완료로 보고하지 않고, 실패 단계와 HTTP 상태만 민감값 없이 보고한다. 이미 쓴 변경은 자동 삭제하거나 되돌리지 않는다.
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```text
|
||||
Confluence Lab2 docs
|
||||
- operation: <create|update|review|sync-token-metadata-table>
|
||||
- page: <page id and title>
|
||||
- parent_folder: 650886407
|
||||
- page_url: <canonical URL>
|
||||
- result: <created|updated|reviewed|blocked>
|
||||
- verification: <folder/auth/page re-read result>
|
||||
- review_notes: <none or concise findings>
|
||||
- raw_token_reported: no
|
||||
```
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- `token/.lgu-atlassian-token`의 원문 또는 Authorization header를 출력, 추적, 공유하지 않는다.
|
||||
- `atlassian_user`에 `leedongmyung@lguplus.co.kr` 이외의 값이나 Git 사용자 정보를 사용하지 않는다.
|
||||
- `sync-token-metadata-table`은 raw token, token hash, Authorization 값, provider credential, draft share URL을 Confluence storage table·payload·첨부 파일·로그·최종 보고에 쓰지 않는다.
|
||||
- 기본 Lab2 폴더 밖에 새 문서를 만들거나 기존 문서를 이동하지 않는다. 예외는 사용자가 명시한 대상뿐이다.
|
||||
- 제목이 모호하거나 동시 변경 충돌을 확인하지 못한 상태에서 update하지 않는다.
|
||||
- review 요청만으로 쓰기 API를 호출하지 않는다.
|
||||
- 삭제, purge, 폴더 삭제, 권한 변경을 이 스킬 범위에 포함하지 않는다.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: openai-usage-token-issue
|
||||
version: 1.0.0
|
||||
description: OpenAI-compatible usage metering용 IOP token을 principal_ref/internal alias에 연결해 발급하는 운영 절차
|
||||
version: 1.0.6
|
||||
description: OpenAI-compatible 사용자 추가와 usage metering용 IOP token을 발급하고 private Edge mapping, local secret store, 사용자가 승인한 Confluence metadata table을 동기화하는 운영 절차
|
||||
---
|
||||
|
||||
# openai-usage-token-issue
|
||||
|
|
@ -10,24 +10,53 @@ description: OpenAI-compatible usage metering용 IOP token을 principal_ref/inte
|
|||
|
||||
OpenAI-compatible 사용량 metering에 쓸 IOP bearer token을 발급하고, raw token 없이 `token_ref`, token hash, `principal_ref`, 내부 alias 매핑만 운영 기록에 남긴다.
|
||||
IOP는 사용자/테넌트 source of truth를 소유하지 않고, 외부 principal id 또는 내부 운영 id를 참조값으로만 다룬다.
|
||||
이 스킬은 dev-corp에서 사용자 추가와 token 발급을 함께 요청받았을 때의 단일 진입점이다.
|
||||
|
||||
## 언제 호출할지
|
||||
|
||||
- OpenAI-compatible 호출 사용량을 특정 `principal_ref` 또는 내부 alias로 귀속할 IOP token을 새로 발급할 때
|
||||
- dev/dev-corp 운영자가 Grafana 사용량 label에 노출될 `token_ref`, `principal_alias`를 준비할 때
|
||||
- raw bearer token을 tracked 파일이나 최종 보고에 남기지 않고 1회 전달해야 할 때
|
||||
- dev-corp 사용자 추가와 함께 해당 사용자의 IOP token 발급·private Edge mapping 반영을 요청받았을 때
|
||||
|
||||
## 입력
|
||||
|
||||
- `operation`: 사용자 추가와 함께 발급할 때는 `create`를 사용한다. token만 추가하거나 회전할 때는 작업 의도를 명시한다. (기본: `create`)
|
||||
- `env`: 대상 환경. 사용자 추가 발급 기본값은 `dev-corp`이다. (기본: `dev-corp`)
|
||||
- `principal_ref`: 외부 사용자/테넌트 프로젝트 또는 운영 시스템의 principal 참조값 (필수)
|
||||
- `principal_alias`: Grafana에 노출할 내부 alias. 없으면 `principal_ref`에서 secret이 아닌 짧은 별칭을 정한다. (선택)
|
||||
- `token_ref`: metric label과 설정에 쓸 안정 token 참조값. 한 `principal_ref`가 여러 앱/통합을 운영하면 앱/통합/용도별로 서로 다른 `token_ref`를 발급한다. 없으면 token hash prefix로 만든다. (선택)
|
||||
- `output_path`: raw token 없이 매핑 기록을 저장할 비공개/운영 전용 파일 경로. tracked docs/config에 쓰지 않는다. (선택)
|
||||
- `output_path`: raw token을 보관해야 한다면 이 프로젝트 repo root의 gitignored `token/.dev-corp-iop-token`을 사용한다. 이 파일 외의 운영 기록은 raw token 없이 남긴다. (선택)
|
||||
|
||||
## 이 프로젝트의 local secret store
|
||||
|
||||
- `token/.dev-corp-iop-token`은 repo root의 operator-local 파일이며, 한 줄에 `<private-user-key>: <raw-token>`을 기록한다. 기본 `private-user-key`는 요청자가 지정한 식별자다.
|
||||
- 발급 전에 `.gitignore`에 정확한 `/token/` 항목이 있는지 확인하고, 없으면 token directory 경로만 추가한다. raw token이나 사용자 값 자체를 `.gitignore`에 쓰지 않는다.
|
||||
- 파일은 `umask 077`으로 만들거나 유지해 mode `0600`이어야 한다. 기존 기록을 보존하기 위해 `token/` 안의 후보 파일을 만든 뒤 검증하고 원자적으로 교체한다.
|
||||
- 요청자가 이메일을 private-user-key 또는 dev-corp `principal_ref`로 명시하면, 그 이메일은 이 ignored local store와 private Edge 설정에만 둔다. tracked 파일, 스킬 예시, 최종 보고에는 실제 이메일을 쓰지 않는다.
|
||||
|
||||
## 승인된 Confluence metadata table 동기화
|
||||
|
||||
- 사용자가 사용자별 발급 현황을 특정 Confluence path에 표로 동기화하라고 명시했을 때만 target URL을 ignored operator-local 파일 `token/.dev-corp-iop-confluence-target`에 한 줄로 보관한다. 이 파일도 mode `0600`, ignored, untracked여야 하며, tracked 스킬·문서·최종 보고에는 draft share URL 또는 식별자를 쓰지 않는다.
|
||||
- Confluence 갱신 전에는 local secret store의 private-user-key 집합과 active `openai.principal_tokens[]`의 `principal_ref` 집합을 1:1로 대조한다. local store에 없는 service/smoke principal은 표에서 제외하며, 불일치 또는 중복이면 표를 쓰지 않고 불일치를 보고한다.
|
||||
- 표의 source of truth는 local secret store의 private-user-key와 active Edge의 `principal_ref`, `principal_alias`, `token_ref`, 활성 상태다. 표는 `사용자`, `principal alias`, `token ref`, `상태`, `동기화 시각` 열만 사용하고 행은 `principal_ref`로 upsert한다. raw token, token hash, Authorization 값, provider credential은 storage body와 API payload에 넣지 않는다.
|
||||
- 실제 갱신은 `lgucorp-confluence-docs` 스킬의 `sync-token-metadata-table` 절차를 따른다. HTTP Basic 인증과 table JSON payload는 stdin 또는 동등한 비노출 stream으로 전달하며 raw token을 shell 인자 또는 payload에 넣지 않는다.
|
||||
- target draft/page는 id, title, status, version, parent, storage body를 먼저 읽는다. body가 비어 있으면 관리 섹션과 표를 쓰고, 비어 있지 않은 body는 `IOP 사용자 토큰 발급 현황` 관리 섹션만 교체한다.
|
||||
- Confluence write 뒤에는 같은 content id를 다시 읽어 title·status·version·parent와 metadata 행 수·사용자/alias/ref/status 값을 active mapping과 내부 비교한다. 인증·권한·version conflict·재조회 실패 시 raw token store나 Edge mapping을 변경하거나 write를 자동 재시도하지 않는다.
|
||||
|
||||
## 사용자 추가(create) 기준
|
||||
|
||||
- 요청자가 이메일을 명시하면 그 값은 private-user-key와 dev-corp `principal_ref`로만 사용한다. tracked 문서·스킬 예시·최종 보고에는 실제 값을 쓰지 않는다.
|
||||
- `principal_alias`는 요청자 식별에 쓸 짧은 ASCII alias로 정하고, `token_ref`는 `iop-dev-corp-<alias>`처럼 환경과 용도를 포함한 안정값으로 정한다.
|
||||
- active private Edge config에서 같은 `principal_ref` 또는 `token_ref`를 먼저 찾는다. 하나라도 있으면 새 raw token을 발급하지 않고 기존 매핑의 활성 상태를 확인한다.
|
||||
- `operation=create`은 `iop-user-crud-ops`의 create 책임과 함께 적용하되, raw token 생성·local secret store·principal token 매핑 절차는 이 스킬이 소유한다.
|
||||
|
||||
## 먼저 확인할 것
|
||||
|
||||
- [ ] `principal_ref`가 secret, raw email, provider token, provider identity가 아니라 외부 시스템 참조값인지 확인한다.
|
||||
- [ ] `principal_ref`가 secret, provider token, provider identity가 아니라 외부 시스템 참조값인지 확인한다. 운영자가 private dev-corp 매핑의 이메일을 명시한 경우에는 ignored local store와 private Edge 설정으로 범위를 제한한다.
|
||||
- [ ] raw token을 tracked `docs/`, `agent-roadmap/`, `agent-spec/`, `configs/`, git diff, shell history, 최종 보고에 남기지 않을 전달 경로를 정한다.
|
||||
- [ ] raw token 보관이 필요한 경우 `token/.dev-corp-iop-token`의 ignore 상태와 mode `0600`을 확인한다.
|
||||
- [ ] Confluence 동기화를 요청받은 경우 `token/.dev-corp-iop-confluence-target`의 mode `0600`, ignored, untracked 상태와 대상 page 접근을 확인한다.
|
||||
- [ ] `token_ref`와 `principal_alias`가 낮은 cardinality label로 안전한 값인지 확인한다.
|
||||
- [ ] 같은 `principal_ref`에 여러 앱/통합용 token이 필요한 경우 각 token의 앱/통합/용도 구분이 `token_ref`에 반영되는지 확인한다.
|
||||
- [ ] 기존 token을 회전하는 경우 기존 `token_ref`를 재사용할지 새 `token_ref`를 만들지 운영 정책을 확인한다.
|
||||
|
|
@ -38,6 +67,7 @@ IOP는 사용자/테넌트 source of truth를 소유하지 않고, 외부 princi
|
|||
- `principal_ref` 앞뒤 공백을 제거한다.
|
||||
- `principal_alias`는 공백을 `-`로 바꾸고, 운영자가 식별할 수 있는 짧은 ASCII alias로 둔다.
|
||||
- `token_ref`를 직접 받지 않았으면 생성할 token hash의 앞 16자를 사용해 `ioptok_<hash-prefix>` 형식으로 만든다.
|
||||
- `operation=create`이면 raw token 생성 전에 active private Edge config의 `principal_ref`와 `token_ref` 중복을 확인한다. 기존 매핑이 있으면 발급을 중단하고 활성 상태를 검증한다.
|
||||
|
||||
2. **raw token 생성**
|
||||
- 현재 shell에서 `set +x`를 확인한다.
|
||||
|
|
@ -51,9 +81,19 @@ token_hash="$(printf '%s' "$raw_token" | sha256sum | awk '{print $1}')"
|
|||
token_ref="${token_ref:-ioptok_${token_hash:0:16}}"
|
||||
```
|
||||
|
||||
3. **매핑 기록 작성**
|
||||
- raw token은 파일에 쓰지 않는다.
|
||||
- 운영 기록에는 아래 필드만 남긴다.
|
||||
3. **local secret store 갱신**
|
||||
- raw token을 보관해야 할 때만 repo root의 `token/.dev-corp-iop-token`에 `<private-user-key>: <raw-token>` 한 줄을 추가한다.
|
||||
- 새 파일 또는 후보 파일은 `umask 077`으로 만들고, 기존 매핑을 보존한 뒤 mode `0600` 및 ignore 상태를 검증한다.
|
||||
- raw token을 shell 인자, trace, 화면 출력, tracked diff에 노출하지 않는다.
|
||||
|
||||
4. **사용자 추가와 private Edge 후보 반영**
|
||||
- `operation=create`이면 active private Edge config의 `openai.principal_tokens[]`에서 `principal_ref`와 `token_ref` 중복을 다시 확인한다.
|
||||
- local secret store는 `token/` 안의 후보 파일에 기존 레코드와 새 `<private-user-key>: <raw-token>`을 함께 기록하고, mode `0600`·ignore 상태를 확인한 뒤 원자적으로 교체한다.
|
||||
- raw token은 SSH 표준 입력처럼 비노출 stream으로만 private Edge config 후보 생성기에 전달한다. 후보 config에는 `token_ref`, SHA-256 hash, `principal_ref`, `principal_alias`만 기록한다.
|
||||
- `openai.principal_tokens[]` 변경은 restart-required다. 환경 배포 스킬의 config check·refresh dry-run·backup/mv cutover·Edge restart 절차를 따른다. live apply 완료로 처리하지 않는다.
|
||||
|
||||
5. **raw-token-free 운영 기록 작성**
|
||||
- tracked 문서나 공유 운영 기록에는 아래 필드만 남긴다.
|
||||
|
||||
```yaml
|
||||
token_ref: "<token_ref>"
|
||||
|
|
@ -63,12 +103,20 @@ token_hash_sha256: "<token_hash>"
|
|||
status: active
|
||||
```
|
||||
|
||||
4. **raw token 1회 전달**
|
||||
6. **승인된 Confluence metadata table 동기화**
|
||||
- 동기화 요청이 있을 때만 local secret store의 private-user-key와 active Edge `principal_tokens[]`를 `principal_ref` 기준으로 1:1 대조한다. 불일치 또는 중복이면 표를 쓰지 않고 `blocked`로 보고한다.
|
||||
- target URL은 `token/.dev-corp-iop-confluence-target`에서만 읽고, `lgucorp-confluence-docs`의 `sync-token-metadata-table` 인증·대상 재조회·version 기반 update 절차를 사용한다.
|
||||
- 표에는 `사용자`, `principal alias`, `token ref`, `상태: active`, `동기화 시각`만 넣는다. raw token, token hash, Authorization 값, provider credential, draft share URL은 넣지 않는다.
|
||||
- target body가 비어 있으면 관리 섹션과 표를 쓰고, 비어 있지 않으면 `IOP 사용자 토큰 발급 현황` 관리 섹션만 대체한다. 다른 섹션, title, parent, 권한은 변경하지 않는다.
|
||||
- 갱신 후 page 재조회로 table의 행 수·사용자/alias/ref/status metadata를 active mapping과 내부 검증한다. 실패하면 결과를 `blocked`로 보고하고, 앞선 token/Edge 상태를 되돌리거나 새 token을 발급하지 않는다.
|
||||
|
||||
7. **raw token 1회 전달**
|
||||
- raw token은 operator-only 채널로 한 번만 전달한다.
|
||||
- 채팅 최종 보고, git diff, tracked 문서, 검증 출력에는 raw token을 쓰지 않는다.
|
||||
|
||||
5. **누출 확인**
|
||||
8. **누출 확인**
|
||||
- 저장소 안에 raw token이 남지 않았는지 조용한 검색으로 확인한다. 실패 시 출력에 raw token이 찍히지 않게 한다.
|
||||
- 검사 대상에서 의도된 ignored `token/` directory는 제외하되, 그 안의 secret store가 추적되지 않았음을 별도로 확인한다.
|
||||
|
||||
```bash
|
||||
if rg -q -F "$raw_token" agent-ops agent-roadmap agent-spec agent-contract docs configs apps packages proto; then
|
||||
|
|
@ -78,25 +126,35 @@ fi
|
|||
echo "raw token not found in tracked workspace paths"
|
||||
```
|
||||
|
||||
6. **결과 보고**
|
||||
- `token_ref`, `principal_ref`, `principal_alias`, 매핑 기록 위치, raw token 전달 여부만 보고한다.
|
||||
9. **결과 보고**
|
||||
- `token_ref`, `principal_ref`, `principal_alias`, 매핑 기록 위치, Confluence metadata table 동기화 상태, raw token 전달 여부만 보고한다.
|
||||
- raw token과 전체 token hash는 보고하지 않는다.
|
||||
|
||||
## 실행 결과 검증
|
||||
|
||||
- [ ] `operation=create`의 active private Edge config에 대상 `principal_ref`와 `token_ref`가 각각 한 번만 존재하는가
|
||||
- [ ] raw token이 operator에게 1회만 전달되었는가
|
||||
- [ ] tracked 파일에는 raw token이 없고, `token_ref`, `principal_ref`, `principal_alias`, hash만 남았는가
|
||||
- [ ] `token/.dev-corp-iop-token`이 mode `0600`이고 `git check-ignore -q token/.dev-corp-iop-token`을 통과하며 `git ls-files --error-unmatch token/.dev-corp-iop-token`이 실패하는가
|
||||
- [ ] Confluence 동기화를 요청한 경우 `token/.dev-corp-iop-confluence-target`이 mode `0600`, ignored, untracked이고, local private-user-key 집합과 active Edge `principal_ref` 집합이 일치하는가
|
||||
- [ ] Confluence table은 local secret store의 각 사용자당 정확히 한 행이고 `사용자`, alias, ref, status metadata가 active mapping과 일치하며 raw token·token hash를 포함하지 않는가
|
||||
- [ ] Confluence 갱신 뒤 같은 draft/page의 title, status, parent가 유지되고 version·table 행 수가 기대값으로 재조회되는가
|
||||
- [ ] Edge restart 후 새 token으로 `/v1/models`와 `/v1/chat/completions` 인증이 성공하고, 가능한 경우 usage metric의 principal/token label이 관측되는가
|
||||
- [ ] `rg -q -F "$raw_token" ...` 누출 확인이 실패하지 않았는가
|
||||
- [ ] 최종 보고에 raw token, provider token, provider identity, raw prompt/response가 포함되지 않았는가
|
||||
- 검증 실패 시: raw token을 폐기하고 새 token을 발급한다. 누출된 tracked 파일은 수정한 뒤 다시 누출 확인을 실행한다.
|
||||
- 검증 실패 시: raw token을 폐기하고 새 token을 발급한다. 누출된 tracked 파일은 수정한 뒤 다시 누출 확인을 실행하고, local secret store는 새 후보 파일 검증 뒤에만 교체한다.
|
||||
|
||||
## 출력 형식
|
||||
|
||||
```text
|
||||
OpenAI usage token issue
|
||||
- operation: <create|rotate-token|other>
|
||||
- token_ref: <token_ref>
|
||||
- principal_ref: <principal_ref>
|
||||
- principal_alias: <principal_alias>
|
||||
- secret_store: token/.dev-corp-iop-token
|
||||
- edge_mapping: <candidate-validated-and-restarted|existing-verified|not-applied>
|
||||
- confluence_metadata_sync: <updated|not-requested|blocked>
|
||||
- mapping_record: <path or operator-private store>
|
||||
- raw_token_delivered_once: <yes|no>
|
||||
- leak_check: <pass|fail>
|
||||
|
|
@ -106,6 +164,10 @@ OpenAI usage token issue
|
|||
## 금지 사항
|
||||
|
||||
- raw token을 tracked 파일, 최종 보고, 로그, metric label, Grafana dashboard, shell trace에 남기지 않는다.
|
||||
- raw token 또는 실제 요청자 식별자를 `.gitignore`, 스킬 예시, tracked 문서, shell 인자에 쓰지 않는다. `.gitignore`에는 `/token/` directory 경로만 둔다.
|
||||
- raw token, token hash, Authorization 값, provider credential, draft share URL을 Confluence metadata table 또는 Confluence API payload에 넣지 않는다.
|
||||
- local secret store와 active Edge mapping이 1:1로 대조되지 않은 상태에서 Confluence 표를 갱신하거나, 관리 섹션 밖의 본문·title·parent를 변경하지 않는다.
|
||||
- active config에 같은 `principal_ref` 또는 `token_ref`가 있는데 새 raw token을 발급하거나 중복 entry를 추가하지 않는다.
|
||||
- `metadata.user`, provider token, provider identity를 사용자 식별 source로 쓰지 않는다.
|
||||
- `request_id`, `session_id`, raw token, raw prompt, raw response 같은 high-cardinality 또는 secret 값을 metric label 후보로 만들지 않는다.
|
||||
- 사용자 CRUD, tenant/org source of truth, token 제한 enforcement를 이 스킬 책임으로 확장하지 않는다.
|
||||
|
|
|
|||
Loading…
Reference in a new issue