Pipeline Studio 비-admin 코드 작성 QA 가이드 — 페르소나별 시나리오
이 문서는 #2106 P1(비-admin 워크스페이스 멤버의 코드 노드 작성 — 승인 게이트 기반)을 페르소나별로 실제 브라우저에서 직접 검증하는 가이드입니다. 모든 화면은 AKS prod(gend.genon.ai)의 실 워크스페이스(finance-invest / energy-kogas)에서, 각 역할의 실제 토큰으로 캡처했습니다. API 우회 없이 전부 UI로 수행했습니다.
이전 가이드와의 차이 — 코드 노드 QA 가이드는 "코드 노드 추가·배포는 관리자만" 시점의 문서입니다. 본 가이드는 그 이후 추가된 비-admin 경로(드라이런 허용 + ws-admin 승인 + library_ref-only 배포)를 다룹니다. 게이트는 완화하지 않고 경로만 추가됐습니다.
등장 페르소나
| 페르소나 | 권한 | Keycloak 그룹 | 할 수 있는 일 |
|---|---|---|---|
| 워크스페이스 멤버 | 비-admin | /tenants/<slug> | 코드 드라이런, 스텝 제출(승인 대기), 승인된 라이브러리 스텝으로 배포 |
| 워크스페이스 admin | ws-admin | /tenants/<slug>/admins | 위 + 스텝 승인/거부 |
| 글로벌 admin | admin | (realm role) | 전 워크스페이스 무제한 (인라인 코드 직접 배포 포함) |
보안 불변식 (이 가이드가 검증하는 것)
- I1 — 미승인/인라인 코드는 비-admin 으로 절대 배포되지 않는다.
- I3 — 드라이런은 격리 샌드박스 전용 + 워크스페이스 멤버십 펜스 + per-ws rate limit.
- I4 — ws-admin 은 다른 테넌트의 스텝을 승인할 수 없다(워크스페이스 펜스).
1. 멤버 — 코드 드라이런 (I3)
워크스페이스 멤버는 admin 이 아니어도 코드 노드를 캔버스에 놓고 드라이런으로 즉시 테스트할 수 있습니다. 격리 러너(gend-codexec)에서 실행되고, 결과(status: ok · output · metrics)가 바로 보입니다.

확인 포인트 —
import socket같은 정책 위반 코드는 드라이런 단계에서 L1 AST 검사로 거부됩니다. 토스트에python 코드 정책 위반 [import] line 1: socket이 뜹니다.

rate limit — 드라이런은 워크스페이스 단위 토큰 버킷(기본
rps=0.5,burst=5)으로 보호됩니다. 한 멤버가 러너를 연타하면 같은 워크스페이스 버킷을 공유하므로 429 로 제한됩니다. (UI 에서는 "드라이런 중…" 동안 버튼이 비활성화돼 연타 자체가 직렬화됩니다 — rate limit 은 다중 멤버 동시 남용 방어용입니다.)
2. 멤버 — 인라인 코드 배포는 차단 (I1)
멤버가 캔버스에 인라인 코드 노드(library_ref 없이 직접 작성한 shell/python)를 넣고 배포를 시도하면 403 으로 막힙니다.

토스트: "코드 노드는 워크스페이스 관리자만 추가/배포할 수 있습니다". 인라인 코드를 배포하려면 아래 승인 흐름을 거쳐야 합니다.
3. 멤버 — 재사용 스텝 제출 (승인 대기)
멤버는 스텝 라이브러리 탭에서 재사용 스텝을 제출할 수 있습니다. shell/python 스텝은 승인 대기(pending) 상태로 등록됩니다.

멤버 화면에는 승인/거부 버튼이 없습니다 — 승인은 ws-admin 의 권한입니다.
4. ws-admin — 스텝 승인/거부
워크스페이스 admin(/tenants/<slug>/admins)에게는 같은 목록에 ✓ 승인 / ✗ 거부 버튼이 보입니다.

✓ 승인을 누르면 배지가 대기중 → 승인됨 으로 바뀝니다.

✗ 거부를 누르면 거부됨 으로 바뀝니다.

모든 승인/거부는
ApprovalRequest(request_type="ps_step_approval")감사 레코드로 기록됩니다.
4-1. 크로스테넌트 펜스 (I4)
finance-invest 의 ws-admin 은 energy-kogas 의 스텝을 볼 수도, 승인할 수도 없습니다. 아래 화면은 finance-invest ws-admin 의 스텝 목록 — 같은 화면에 finance 스텝은 보이지만, energy-kogas 멤버가 만든 스텝은 목록에 아예 없습니다(워크스페이스 펜스).

서버에서
decide_step_approval이 호출자 워크스페이스로 펜스하므로, 타 테넌트 스텝 ID 로 직접 승인 요청해도 404 입니다.
5. 멤버 — 승인된 스텝으로 배포 (library_ref-only)
스텝이 승인되면, 멤버의 빌더 노드 팔레트 "라이브러리" 섹션에 나타납니다. (승인된 스텝만 — 거부/대기 스텝은 제외.)

이 라이브러리 노드를 캔버스로 드래그해 구성한 파이프라인은, 코드노드가 전부 승인된 library_ref 이므로 멤버도 배포 가능합니다(배포됨 (rev 1)).

5-1. 실행 + per-node 로그 (#2107)
배포본을 실행하면 실행 탭에서 각 노드의 로그(stdout/stderr)를 펼쳐볼 수 있습니다. 아래는 실패한 노드의 로그 디스클로저 — 코드 러너의 stderr 와 실패 사유가 그대로 노출됩니다.

계약 주의 — shell/python 코드는 stdout 에 JSON 을 출력해야 합니다. 일반 텍스트를 출력하면
code execution failed: output not JSON으로 실패합니다(위 화면).
5-2. 승인 코드 재정의는 차단 (I1 강화)
멤버가 인스턴스화한 라이브러리 노드의 코드를 편집해서 재배포하려 하면 차단됩니다 — 승인된 코드를 우회 변경할 수 없습니다.

토스트: "승인된 라이브러리 스텝의 코드는 재정의할 수 없습니다 — 재승인이 필요합니다 (관리자만)". (배포 게이트가 원본 그래프의 노드 코드와 승인된 스텝 코드를 대조해 거부합니다.)
6. 멤버 — 인라인 코드 배포-요청 (P2, #2106 경로 B)
P1(§1~5)은 멤버가 승인된 라이브러리 스텝으로만 배포하는 경로였습니다. P2 는 멤버가 인라인 코드를 그대로 배포하고 싶을 때 — 직접 배포는 여전히 막되(I1) ws-admin 승인 경로로 prod 에 도달하게 합니다.
6-1. 멤버 — 인라인 코드 파이프라인 + 직접 배포 차단 (I1)
멤버가 인라인 python 코드 노드로 파이프라인을 구성합니다.

배포를 직접 누르면 P1 과 동일하게 403 — 게이트는 완화되지 않습니다.

6-2. 멤버 — 배포 요청 제출
대신 배포 요청 버튼을 누르면, 현재 드래프트가 불변 snapshot(content_hash 포함)으로 굳어 ws-admin 승인 큐에 올라갑니다.

토스트: "배포 요청을 제출했습니다 — 관리자 승인 대기".
엣지 — 인라인 코드가 없는 파이프라인에서 배포 요청을 누르면 "인라인 코드 노드가 없습니다 — 직접 배포할 수 있습니다"(422)로 안내합니다.
6-3. ws-admin — 배포 요청 큐 + 코드 검토
ws-admin 에게는 Pipeline Studio 목록에 "배포 요청" 탭이 보입니다(멤버에겐 미노출). 제출된 요청과 실제 제출 코드를 그대로 검토합니다.


6-4. ws-admin — 승인·배포 (I2: 승인된 코드 == 배포된 코드)
승인·배포를 누르면 요청 시점의 불변 snapshot 이 배포됩니다. 멤버가 요청 후 드래프트 코드를 바꿔도, 배포되는 것은 승인 시점에 검토한 코드입니다(content_hash 무결성 + 승인 시 AST 재검증).

prod 검증: 요청 후 드래프트를
DRAFT_CHANGED로 바꾼 뒤 승인 → 배포된 리비전 코드는 요청 시점 원본(p2-member-original)이었고 변경분은 반영되지 않았습니다(I2).
6-5. 크로스테넌트 펜스 (I4)
다른 테넌트(energy-kogas) 멤버가 올린 배포 요청은 finance-invest ws-admin 의 큐에 보이지 않습니다. 승인 권한이 워크스페이스로 펜스됩니다.

검증 매트릭스 (실 브라우저 prod 결과)
| # | 시나리오 | 페르소나 | 기대 | 결과 |
|---|---|---|---|---|
| 1 | 코드 드라이런 | 멤버 | status: ok | ✅ |
| 2 | 정책 위반(import socket) 드라이런 | 멤버 | AST 거부 | ✅ |
| 3 | 인라인 코드 직접 배포 | 멤버 | 403 차단 | ✅ |
| 4 | 스텝 제출 | 멤버 | 대기중, 버튼 없음 | ✅ |
| 5 | 스텝 승인 | ws-admin | 승인됨 | ✅ |
| 6 | 스텝 거부 | ws-admin | 거부됨 | ✅ |
| 7 | 타 테넌트 스텝 | ws-admin | 미표시(펜스) | ✅ |
| 8 | library_ref-only 배포 | 멤버 | 배포 성공 | ✅ |
| 9 | per-node 실행 로그 | 멤버 | stdout/stderr 노출 | ✅ |
| 10 | 승인 코드 재정의 배포 | 멤버 | 차단 | ✅ |
| 11 | P2 인라인 배포 요청 제출 | 멤버 | 승인 대기 | ✅ |
| 12 | P2 인라인 코드 없음 → 요청 | 멤버 | 422 안내 | ✅ |
| 13 | P2 배포 요청 탭 | 멤버 | 미노출 | ✅ |
| 14 | P2 배포 요청 큐 + 코드 검토 | ws-admin | 코드 표시 | ✅ |
| 15 | P2 승인·배포(불변 snapshot) | ws-admin | 요청 시점 코드 배포 (I2) | ✅ |
| 16 | P2 타 테넌트 배포 요청 | ws-admin | 미표시(펜스 I4) | ✅ |
P1 스크린샷(§1~5)은 2026-06-12, P2 스크린샷(§6)은 2026-06-13 AKS prod 실 브라우저 세션에서 모든 기능 동작을 UI 로만(API 우회 없이) 수행해 캡처했습니다. 단위/통합 테스트는
apps/api/tests/test_auth_workspace_admin.py,test_ps_step_approval.py,test_ps_code_gate.py,test_pipeline_studio_code_test.py,test_ps_nonadmin_deploy.py,test_ps_code_deploy_request.py,test_ps_code_deploy_router.py+ui/.../CodeDeployRequestsTab.test.tsx에서 동일 불변식(I1/I2/I3/I4)을 커버합니다.
