{ "schemaVersion": 2, "runId": "2026-09-16-2002", "project": "keycloak", "record": "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "startedAt": "2026-09-16T20:02:41+09:00", "finishedAt": "2026-09-16T20:15:52+09:00", "stages": [ { "id": "S1", "name": "코드베이스 → SSOT", "skill": "analyzing-codebase-for-tech-log", "runBy": "ssot-analyst", "status": "SKIPPED", "skipReason": "SSOT(docs/keycloak/final/document.md)가 이미 있고 이 기록의 근거를 그 안에서 찾을 수 있다. 이번 런은 기록의 칸 배치를 고치는 것이라 SSOT 를 건드리지 않았다.", "skillEcho": "", "skillRevision": "edd45dfec66d155a621cd41186b83b5582f2244c", "inputs": [], "outputs": [], "gates": [], "notes": "", "finishedAt": "2026-09-16T20:05:31+09:00", "finishedBy": null }, { "id": "S2", "name": "SSOT → 분해 계약", "skill": "deriving-tech-log-root-tree", "runBy": "tree-deriver", "status": "SKIPPED", "skipReason": "이 글감은 tech-log-tree.json 에 PROMOTE·CONFIRMED 로 이미 있다. 계약이 아니라 기록 파일 안의 문단 위치를 고쳤다.", "skillEcho": "", "skillRevision": null, "inputs": [], "outputs": [], "gates": [], "notes": "", "finishedAt": "2026-09-16T20:05:31+09:00", "finishedBy": null }, { "id": "S3", "name": "글감 → 기록", "skill": "writing-tech-log-records", "runBy": "record-writer", "status": "DONE", "skipReason": "", "skillEcho": "2. **칸 채우기** — 칸과 게시 조건은 `references/record-kinds.md`.", "skillRevision": null, "inputs": [], "outputs": [ "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md" ], "gates": [ { "cmd": "python3 scripts/studio-body.py docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md -o /tmp/sb.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:05:31+09:00" }, { "cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/sb.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:05:31+09:00" }, { "cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:05:31+09:00" }, { "cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak --repo", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:05:31+09:00" }, { "cmd": "python3 scripts/check-required-content.py --file docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:05:32+09:00" } ], "notes": "둘째 문단 158자를 옮기지 않고 지웠다. 네 칸(근거·결정문·판단 이유·영향)을 전부 읽고 대조했고 네 주장이 이미 흩어져 있었다 — code 교환은 결정문과 판단 이유, 토큰 서버 보관은 영향, 세션으로 BFF 호출은 결정문과 영향, downstream 호출은 판단 이유와 결정문. 삭제 4줄뿐이고 새로 넣은 문장은 없다. 다만 요약 앞 90자가 BFF 의 정의라 결과가 아니다 — 목록 카드 기준으로 약한 것은 남는다.", "startedAt": null, "generation": 1, "owner": null, "elapsedSeconds": 1, "finishedAt": "2026-09-16T20:05:32+09:00", "finishedBy": null }, { "id": "S4", "name": "기록 → 그림", "skill": "technical-visualizer", "runBy": "diagram-maker", "status": "SKIPPED", "skipReason": "그림이 필요 없다. 이번 런은 제목 아래 문단을 알맞은 칸으로 옮기거나 지운 것이라 새로 그릴 소재가 생기지 않았다.", "skillEcho": "", "skillRevision": "edd45dfec66d155a621cd41186b83b5582f2244c", "inputs": [], "outputs": [], "gates": [], "notes": "", "finishedAt": "2026-09-16T20:05:32+09:00", "finishedBy": null }, { "id": "S5", "name": "AI 티 제거", "skill": "rewriting-technical-prose-naturally", "runBy": "prose-rewriter", "status": "DONE", "skipReason": "", "skillEcho": "**Four buckets. Only the first stays in Latin script.**", "skillRevision": null, "inputs": [], "outputs": [ "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md" ], "gates": [ { "cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:10:43+09:00" }, { "cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/style_profile.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "exit": 1, "session": null, "generation": 1, "at": "2026-09-16T20:10:43+09:00" }, { "cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/s5.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:10:43+09:00" }, { "cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak --repo", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:10:44+09:00" } ], "notes": "네 곳을 고쳤다 — 판단 이유의 110자 문장을 주어가 바뀌는 자리에서 끊고, 세 문단 연속 반복되던 「브라우저에 OAuth 토큰을 …」 문형 중 셋째를 「이 요구는 만족할 수 있지만」으로 받게 했다(선행사는 바로 앞 문장). upstream→업스트림, edge→엣지, replica→레플리카, script→스크립트는 형제 기록이 이미 쓰는 형태다. 안 고친 것 — 결정문의 130자 한 문장이 longRatio 를 혼자 만들지만 기록된 결정문이라 두었다. session failover·secret rotation·origin 은 한글로 옮기면 없던 역어를 짓게 되고, 특히 secret rotation 은 같은 칸의 「암호화 키 교체」와 섞인다.", "startedAt": null, "generation": 1, "owner": null, "elapsedSeconds": 1, "finishedAt": "2026-09-16T20:10:44+09:00", "finishedBy": null }, { "id": "S6", "name": "일한 사람의 목소리", "skill": "writing-as-the-person-who-did-it", "runBy": "voice-writer", "status": "DONE", "skipReason": "", "skillEcho": "1. **고른 이유를 견준 대상과 함께 적는다** — 무엇을 놓고 무엇을 골랐고 무엇을 감수했나", "skillRevision": "edd45dfec66d155a621cd41186b83b5582f2244c", "inputs": [], "outputs": [ "docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md" ], "gates": [ { "cmd": "node .agents/skills/writing-as-the-person-who-did-it/scripts/check_voice.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:15:52+09:00" }, { "cmd": "node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:15:52+09:00" }, { "cmd": "node --experimental-transform-types .agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/s6.md", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:15:52+09:00" }, { "cmd": "node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs keycloak --repo", "exit": 0, "session": null, "generation": 1, "at": "2026-09-16T20:15:52+09:00" } ], "notes": "판단 이유에 견준 대안과 감수한 비용 한 문단을 넣었다 — 브라우저가 Resource Server 를 직접 호출하는 경로를 남겨야 하면 Mediator 가 맞고 OAuth 흐름을 브라우저에서 직접 봐야 하면 브라우저가 code 를 직접 교환하는 구조가 맞는데, 이 결정은 그 둘을 포기하는 대신 브라우저에서 OAuth 토큰을 없앤다. 근거는 SSOT:243 이고 source 앵커 안이다. 계약의 classification 이 「AP1·AP2 를 대안으로 두고」라고 적는데 기록의 판단 이유는 AP2·AP4 만 견주고 AP1 쪽 대안이 통째로 없었다. 초안에 SPA 를 썼다가 check_prose 경고가 2→3 으로 늘어(이 기록은 SPA 를 어디서도 안 푼다) 약어를 빼고 「브라우저가 authorization code 를 직접 교환하는 구조」로 고쳐 되돌렸다.", "startedAt": null, "generation": 1, "owner": null, "elapsedSeconds": 0, "finishedAt": "2026-09-16T20:15:52+09:00", "finishedBy": null }, { "id": "S7", "name": "Studio 저장", "skill": "publishing-tech-log-to-studio", "runBy": "studio-validator", "status": "SKIPPED", "skipReason": "사용자가 Studio 반입을 요청하지 않았다. 이 기록은 이미 게시돼 있고 S7 은 저장까지인데, 저장하면 Studio 쪽 version 이 올라간다. 저장소와 공개본이 갈린 상태로 두고 사람이 판단할 일로 넘긴다. 게시는 어떤 경우에도 하지 않는다.", "skillEcho": "", "skillRevision": null, "inputs": [], "outputs": [], "gates": [], "notes": "", "finishedAt": "2026-09-16T20:15:52+09:00", "finishedBy": null } ], "revision": 23, "updatedAt": "2026-09-16T20:15:52+09:00", "sessions": [ { "session": null, "stage": "S3", "generation": 1, "beganAt": "2026-09-16T20:05:31+09:00" }, { "session": null, "stage": "S5", "generation": 1, "beganAt": "2026-09-16T20:10:43+09:00" }, { "session": null, "stage": "S6", "generation": 1, "beganAt": "2026-09-16T20:15:52+09:00" } ] }