Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .issueops/OPEN_API_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: Endpoint, DTO, and OpenAPI documentation gate rules.
Galpi has **no HTTP API and no OpenAPI/Swagger surface**. The contract
surfaces that do exist:

- 24 Tauri IPC commands in `src-tauri/src/adapters/inbound/tauri.rs`,
- 28 Tauri IPC commands in `src-tauri/src/adapters/inbound/tauri.rs`,
consumed through `BackendPort` with Zod-validated responses
(`src/adapters/tauri-backend.ts`).
- Versioned JSONL worker protocol v1 (`worker/galpi_worker/protocol.py` ↔
Expand Down
14 changes: 14 additions & 0 deletions .issueops/adr/2026-10-09-auth-gateway-google.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: 2026-10-09-auth-gateway-google
description: Accepted decision record with rationale, alternatives, and consequences.
---

# 앱 사용에 auth-gateway Google 로그인을 요구하고 호스트가 기능 명령을 막는다

- Date: 2026-10-09
- Kind: `adr`
- Source: issueops-docs io-21fd3b1e1f06 (issue #10); src-tauri/src/application/gateway.rs, src-tauri/src/adapters/outbound/gateway/mod.rs, src-tauri/src/adapters/outbound/browser_sign_in/; cargo test --all-targets gateway (24 passed)
- Summary: Galpi는 auth-gateway의 데스크톱 로그인(RFC 8252: 시스템 브라우저, 루프백 리다이렉트, PKCE)으로 Google 로그인을 마쳐야 쓸 수 있다. Rust Application이 기능 명령 5개(prepare·refine_transcript·transcribe·import_transcript·start_recording)의 첫 줄에서 AUTH_REQUIRED로 막고, 프런트는 inert 셸 앞의 로그인 화면만 보여 준다.
- Context: galpi#10 사용자 결정: auth-gateway에 데스크톱 로그인 흐름을 추가(m16khb-org/auth-gateway#8)하고, 로그인한 모든 Google 계정을 허용하며, 저장된 세션이 있으면 오프라인에서도 쓴다. 기존 ChatGPT 로그인 어댑터가 브라우저·루프백·PKCE를 이미 갖고 있었다.
- Decision: 1) application/gateway.rs AppAccessGate가 메모리 접근 상태를 소유하고, 시작 때 refresh 1회로 판정한다: 성공은 회전 토큰 저장 후 열기, gateway의 400 invalid_grant만 세션 삭제 후 닫기, 그 밖의 실패(연결·5xx·429·형식 오류)는 저장 세션으로 오프라인 열기. 2) 로그아웃은 메모리 상태를 먼저 닫고 POST /auth/native/logout(이 기기 세션만)을 시도한 뒤 결과와 관계없이 로컬 세션을 지운다. 3) 세션은 새 Secret::GatewaySession(Windows Credential Manager com.m16khb.galpi:gateway-session, macOS 0600 settings.json)에, 이메일은 settings.json gatewayEmail에 둔다. 4) ChatGPT와 gateway 로그인이 함께 쓰는 브라우저·루프백·PKCE는 adapters/outbound/browser_sign_in/로 옮기고 오류 코드는 SignInCodes로 호출자가 넘긴다. 5) gateway 주소는 https://auth.m16khb.dev이며 디버그 빌드에서만 GALPI_AUTH_GATEWAY_URL로 바꾼다.
- Consequences: 업데이트 뒤 첫 실행은 로그인 전까지 기능을 쓸 수 없고, 저장 세션이 없는 오프라인 첫 실행은 쓸 수 없다. 새 기능 명령을 추가하면 Application 메서드 첫 줄에 self.access.require()?를 넣어야 한다. 진행 중 작업을 끝내는 명령(stop/cancel)과 설정·진단은 막지 않는다. macOS에서는 refresh token이 서명 전까지 0600 평문 파일에 남는다.
8 changes: 5 additions & 3 deletions .issueops/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ rule, with DDD tactical patterns — applied consistently across three runtimes:
```text
TypeScript (WebView) Rust (Tauri host) Python (WhisperX sidecar)
ui/ outer adapters/inbound/tauri.rs __main__.py outer
application/ application/ (13 ports) engine/refine use cases
application/ application/ (15 ports) engine/refine use cases
domain/ inner domain/ (inner) protocol.py (port: stdout)
adapters/tauri-backend composition.rs (root) domain pure modules
```
Expand All @@ -32,10 +32,12 @@ adapters/tauri-backend composition.rs (root) domain pure modules
- DDD tactical: value objects (`SpeakerHint`, `Participant`, `GlossaryEntry`,
`AssistantSettings`), aggregate root `Artifacts::path_for(kind)`, domain
services as pure functions, conceptual in-memory `JobRegistry`.
- Inbound adapters: `tauri.rs` (24 `#[tauri::command]`s incl. `save_compute_device`, + event bridge),
- Inbound adapters: `tauri.rs` (28 `#[tauri::command]`s incl. `save_compute_device`, + event bridge),
`tauri-backend.ts` (Zod boundary), worker `__main__.py`.
- Outbound adapters: `process.rs` (worker supervision), `NativeRecorder`
(CPAL), settings/paths adapters, worker `EventWriter` (stdout JSONL v1).
(CPAL), settings/paths adapters, `ChatGptOAuthAdapter` and
`GatewayOAuthAdapter` over the shared `browser_sign_in/` loopback and PKCE,
worker `EventWriter` (stdout JSONL v1).
- Platform branching lives only in outbound adapters and `composition.rs`:
outbound code branches on `Os` (`platform.rs`, `Os::current()` is the single
`cfg!(windows)`); `#[cfg]` is reserved for code that cannot compile elsewhere
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: 2026-10-09-new-stored-credential-needs-four-match-arms-and-is-empty
description: Caution record for a solved false case or recurring risk.
---

# 새 Secret 값을 추가하면 LocalSettingsStore의 네 match 분기와 is_empty를 함께 고친다

- Date: 2026-10-09
- Kind: `caution`
- Source: issueops-docs io-21fd3b1e1f06 (issue #10); src-tauri/src/adapters/outbound/settings.rs, src-tauri/src/adapters/outbound/settings/gateway.rs
- Summary: macOS SettingsFile 백엔드는 read가 항상 없음, write가 no-op이고 실제 값은 LocalSettingsStore가 LocalSettings 필드에 직접 쓴다. 새 Secret 변형은 secret()·store_secret()·note_secret_present()·secret_stored()의 match 분기와 LocalSettings 필드, is_empty()를 모두 갱신해야 재시작 뒤에도 남는다.
- Context: galpi#10 계획 1차 리뷰가 찾았다: is_empty()는 필드를 손으로 나열하므로 새 필드를 빠뜨리면 다른 설정이 없는 새 설치에서 로그인 직후 store_settings가 settings.json을 지워 세션이 사라진다. match는 컴파일러가 잡지만 is_empty는 잡지 못한다.
- Resolution: Secret::GatewaySession 추가 때 네 분기와 gateway_email·gateway_session·gateway_session_stored를 is_empty에 넣었고, settings/gateway.rs 테스트가 빈 설정에서 SettingsFile 백엔드로 저장한 세션이 새 store에서 다시 읽히는지와 clear 뒤 파일이 지워지는지를 고정한다.
13 changes: 13 additions & 0 deletions .issueops/cautions/2026-10-09-qa-ipc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: 2026-10-09-qa-ipc
description: Caution record for a solved false case or recurring risk.
---

# 네이티브 런타임 연결 실패는 이제 로그인 화면에 표시되고, 브라우저 QA는 IPC를 가짜로 주입해 확인한다

- Date: 2026-10-09
- Kind: `caution`
- Source: issueops-docs io-21fd3b1e1f06 (issue #10); src/ui/controller.ts, src/ui/access-screen.ts, src/ui/controller.test.ts
- Summary: Google 로그인 필수화 뒤 controller.start()는 이벤트 구독이 실패하면 #app-error 배너 대신 로그인 화면(#access-status)에 재실행 안내를 띄우고 로그인·다시 확인 버튼을 숨긴다. 평범한 브라우저에서는 셸이 inert라 작업 화면을 볼 수 없으므로, 화면 확인은 window.__TAURI_INTERNALS__(invoke·transformCallback)와 __TAURI_EVENT_PLUGIN_INTERNALS__를 init script로 가짜로 넣어 진행한다.
- Context: 2026-08-23 caution은 런타임 연결 실패를 앱 배너로 알린다고 적었다. galpi#10에서 셸은 로그인 전 inert이므로 배너가 보이지 않는다. 또한 app-template에 inert를 정적으로 넣으면 템플릿만 쓰는 happy-dom 테스트(token-guide 포커스 복귀, token-settings)가 포커스를 잃거나 멈춘다.
- Resolution: inert는 AccessScreenView.render가 toggleAttribute로만 건다. 런타임 실패 경로는 controller.test.ts가 로그인 화면 문구·숨은 버튼·signInToGateway 0회로 고정한다. 화면 QA는 Vite 개발 서버 + 가짜 __TAURI_INTERNALS__로 로그인 전·대기·오프라인·로그아웃을 확인했다.
2 changes: 1 addition & 1 deletion .issueops/conventions/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ and `docs/ARCHITECTURE.md`; those win over this overview.

## SOLID as practiced (docs/ARCHITECTURE.md SOLID section)

- Ports exist only at real boundaries (13 Rust traits in
- Ports exist only at real boundaries (15 Rust traits in
`application/ports.rs`, TS `BackendPort`), each with a production adapter
and a test fake.
- Extend by tagged union + exhaustive `match`/`switch`; never absorb new
Expand Down
46 changes: 46 additions & 0 deletions .issueops/issues/10/gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Gates: 10

- [x] G1: 공유 모듈 이동 뒤 ChatGPT 로그인 테스트가 그대로 통과한다
CHECK: cargo test --manifest-path src-tauri/Cargo.toml --all-targets chatgpt
EXPECT: /test result: ok\. [1-9]\d* passed/
EVIDENCE: Running unittests src/lib.rs (src-tauri/target/debug/deps/galpi_lib-8b8b8ffe811d62b8) | Running unittests src/main.rs (src-tauri/target/debug/deps/galpi-3a6bea8a51165c32)
- [x] G2: 접근 게이트 application 테스트 (a)~(i)
CHECK: cargo test --manifest-path src-tauri/Cargo.toml --all-targets gateway
EXPECT: /test result: ok\. [1-9]\d* passed/
EVIDENCE: Running unittests src/lib.rs (src-tauri/target/debug/deps/galpi_lib-8b8b8ffe811d62b8) | Running unittests src/main.rs (src-tauri/target/debug/deps/galpi-3a6bea8a51165c32)
- [x] G3: gateway 어댑터·설정 store 테스트
CHECK: cargo test --manifest-path src-tauri/Cargo.toml --all-targets adapters::outbound
EXPECT: /test result: ok\. [1-9]\d* passed/
EVIDENCE: Running unittests src/lib.rs (src-tauri/target/debug/deps/galpi_lib-8b8b8ffe811d62b8) | Running unittests src/main.rs (src-tauri/target/debug/deps/galpi-3a6bea8a51165c32)
- [x] G4: Rust 서식
CHECK: python3 -c 'import subprocess,sys;r=subprocess.run(["cargo", "fmt", "--manifest-path", "src-tauri/Cargo.toml", "--check"]);print("FMT_OK" if r.returncode==0 else "FMT_FAIL");sys.exit(r.returncode)'
EXPECT: FMT_OK
EVIDENCE: FMT_OK
- [x] G4b: Clippy
CHECK: python3 -c 'import subprocess,sys;r=subprocess.run(["cargo", "clippy", "--manifest-path", "src-tauri/Cargo.toml", "--all-targets", "--", "-D", "warnings"]);print("CLIPPY_OK" if r.returncode==0 else "CLIPPY_FAIL");sys.exit(r.returncode)'
EXPECT: CLIPPY_OK
EVIDENCE: Compiling galpi v0.1.0 (/Users/m16khb/Workspace/galpi.worktrees/10-require-gateway-google-login/src-tauri) | Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.09s
- [x] G4c: Rust 전체 테스트
CHECK: cargo test --manifest-path src-tauri/Cargo.toml --all-targets
EXPECT: /test result: ok\. [1-9]\d* passed/
EVIDENCE: Running unittests src/lib.rs (src-tauri/target/debug/deps/galpi_lib-8b8b8ffe811d62b8) | Running unittests src/main.rs (src-tauri/target/debug/deps/galpi-3a6bea8a51165c32)
- [x] G5: 아키텍처 검사·Biome·tsc
CHECK: python3 -c 'import subprocess,sys;r=subprocess.run(["bun", "run", "check"]);print("CHECK_OK" if r.returncode==0 else "CHECK_FAIL");sys.exit(r.returncode)'
EXPECT: CHECK_OK
EVIDENCE: 138 138 │ } | 139 139 │
- [x] G5b: 프런트 reducer·DOM·Zod 테스트
CHECK: bun test
EXPECT: /(?m)^\s*0 fail$/
EVIDENCE: 549 expect() calls | Ran 201 tests across 27 files. [853.00ms]
- [x] G6: 문서 플랫폼 문구 검사
CHECK: python3 scripts/verify-docs-platform.py
EXPECT: PLATFORM_DOCS_OK
EVIDENCE: PLATFORM_DOCS_OK
- [x] G7: 명령 28·포트 15와 문서 숫자 일치
CHECK: python3 -c 'import re,sys,pathlib as P;t=P.Path("src-tauri/src/adapters/inbound/tauri.rs").read_text().count("#[tauri::command]");q=len(re.findall(r"^pub trait",P.Path("src-tauri/src/application/ports.rs").read_text(),re.M));pats=["24 frontend","24 command","24 IPC","24 Tauri","24 커맨드","24개","24 `#[tauri","13 Rust traits","13 ports","13개 포트","13개 trait","포트 13개"];fs=["AGENTS.md","src-tauri/AGENTS.md","docs/ARCHITECTURE.md",".issueops/conventions/overview.md",".issueops/architecture/overview.md",".issueops/OPEN_API_SPEC.md"];left=[(f,x) for f in fs for x in pats if x in P.Path(f).read_text()];ok=t==28 and q==15 and not left;print("COUNTS_OK" if ok else ("COUNTS_FAIL",t,q,left));sys.exit(0 if ok else 1)'
EXPECT: COUNTS_OK
EVIDENCE: COUNTS_OK
- [x] G8: IPC로 나가는 AppAccess JSON에 토큰이 없다
CHECK: cargo test --manifest-path src-tauri/Cargo.toml --all-targets app_access_serializes_only_state_and_email
EXPECT: /test result: ok\. 1 passed/
EVIDENCE: Running unittests src/lib.rs (src-tauri/target/debug/deps/galpi_lib-8b8b8ffe811d62b8) | Running unittests src/main.rs (src-tauri/target/debug/deps/galpi-3a6bea8a51165c32)
26 changes: 26 additions & 0 deletions .issueops/issues/10/intent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 요청자 의도 계약

- lifecycle: io-21fd3b1e1f06
- issue: https://github.com/m16khb-org/galpi/issues/10
- intent_class: standard

## 원문 요청
galpi를 사용하려면 auth-gateway를 통해 google 로그인을 해야되도록 하려고해 로 진행하자 / (연동 방식 질문 답) 첫번째방법이 일반적으로 사용되는 방법이면 그대로 해줘 / (사용 허용 범위 답) 로그인한 모든 Google 계정 / (오프라인 동작 답) 저장된 세션이 있으면 오프라인에서도 사용

## 해석
Galpi(macOS·Windows 데스크톱)를 쓰려면 auth-gateway를 거친 Google 로그인이 필요하도록 한다. 로그인은 RFC 8252 방식(시스템 브라우저, 루프백 리다이렉트, PKCE)으로 하고, auth-gateway에 새로 추가될 데스크톱 로그인 흐름(일회용 코드 교환, 쿠키 없는 refresh·logout)을 사용한다. 로그인한 모든 Google 계정을 허용하며 역할·도메인 검사는 하지 않는다. refresh token은 OS 비밀 저장소에 보관하고, gateway에 접속할 수 없을 때는 저장된 세션으로 계속 사용하며, gateway가 세션을 명시적으로 거부할 때만 다시 로그인을 요구한다. 로그아웃은 gateway 폐기 요청 후 로컬 세션을 지운다. 기존 ChatGPT 로그인 구현(시스템 브라우저, 루프백, PKCE, 비밀 저장소)을 재사용한다. auth-gateway 쪽 변경은 m16khb-org/auth-gateway의 별도 사이클이 선행 조건이다.

## 성공 기준
- 1) 저장된 Galpi 세션이 없으면 앱은 로그인 화면만 보여 주고, 녹음·전사·가져오기·AI 증강·엔진 준비 Tauri 명령은 AUTH_REQUIRED 오류로 거부한다(Rust application 테스트). 2) Google로 로그인 → 시스템 브라우저 → gateway → 루프백 콜백 → 코드 교환 → 토큰을 OS 비밀 저장소에 저장 → 기능 화면 진입(어댑터 테스트 + 실제 gateway 로그인 smoke). 3) 시작 시 refresh: 성공하면 회전된 토큰 저장, gateway가 invalid_grant로 거부하면 세션 삭제 후 로그인 화면, 네트워크 오류·5xx면 저장된 세션으로 계속 사용(포트 테스트로 세 분기 고정). 4) 로그아웃은 gateway 폐기를 시도하고 로컬 세션을 지운 뒤 로그인 화면으로 돌아간다. 5) 로그인 진행 중 취소와 5분 타임아웃을 지원한다. 6) bun run check, bun test, cargo fmt/clippy/test, 워커 게이트 통과.

## 비목표
- (없음)

## 제약
- (없음)

## 모호함
- (없음)

## 읽는 규칙
이 문서는 요청자 의도 계약이다. 원격 이슈 본문은 구현 계약이다. 두 문서가 충돌하면 구현을 시작하지 말고 충돌한 줄을 인용해 blocker로 보고한다.
23 changes: 23 additions & 0 deletions .issueops/issues/10/plan-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 계획 검토 기록

## 1차: 수정 요청

- 1차 리뷰(독립 reviewer 서브에이전트, anthropic/claude-opus-5-5 high)는 수정 요청을 냈다. 설정 파일 is_empty와 macOS 설정 파일 비밀 경로를 빠뜨려 재시작 후 세션이 사라질 수 있고, domain이 AppError에 의존하며, refresh 거부 판정이 invalid_grant보다 넓고, 접근 상태 확인·로그아웃 실패 전이가 없고, gateway가 Supabase 장애를 invalid_grant로 돌려준다는 5건과 문서 목록 누락 1건이다. 이 기록은 반영한 개정본에 묶여 있다.

## 2차: 수정 요청

- 2차 delta 리뷰(독립 reviewer 서브에이전트, anthropic/claude-opus-5-5 high)는 1차 결함 1·2·3·5가 해소됐다고 확인했다. 네이티브 구독 실패 경로에서 로그인 화면이 확인 중에 멈추는 문제와 G7 정규식이 한국어 포트 수 표기를 놓치는 문제 2건으로 수정을 요청했다.

## 3차: 수정 요청

- 3차 delta 리뷰(상향 모델 anthropic/claude-opus-5-5 xhigh, omp -p 빈 컨텍스트)는 2차 지적 두 건이 해소됐다고 확인했다. 런타임 연결 실패 상태의 로그인 버튼 표시 규칙이 계획 안에서 서로 다르고, G7이 .issueops/OPEN_API_SPEC.md의 IPC 명령 수를 놓친다는 2건으로 수정을 요청했다.

## 4차: 수정 요청

- 4차 delta 리뷰(상향 모델 anthropic/claude-opus-5-5 xhigh)는 3차 결함 두 건이 해소됐다고 확인했다. 로그인 버튼 규칙이 진행 중 상태(signing-in·signing-out)를 제외하지 않아 전이표와 어긋나고, 로그아웃이 진행 중인 로그인을 먼저 취소하지 않아 상태 경쟁이 생길 수 있다는 1건으로 수정을 요청했다.

## 5차: 수정 요청

생략: 리뷰 5라운드 상한에 도달했다. 마지막 결함은 리뷰어가 제시한 최소 수정 그대로 반영했다: cancel_sign_in은 ChatGPT(sign_in.rs:48-65)처럼 oneshot 신호만 보내고 상태·저장소를 바꾸지 않으며, 상태 변경·revoke·clear는 sign_out에만 둔다. 이를 고정하는 application 테스트 (i)(저장 세션이 있는 상태에서 로그인 취소 → revoke 0회, 세션 유지, CANCELLED)를 G2에 추가했다. 같은 개정에서 게이트를 gates-ledger 형식(argv 명령 하나, 셸 연결자 없음)으로 바꾸고 G7 래퍼를 구현 전 저장소에서 실행해 COUNTS_FAIL(24, 13)로 판정되는 것을 확인했다.

- 5차 delta 리뷰(상향 모델 anthropic/claude-opus-5-5 xhigh)는 4차 결함(버튼 허용 목록, 로그아웃 전 로그인 취소)이 해소됐다고 확인했다. 남은 결함은 cancel_sign_in이 저장 세션을 지우는 동작으로 정의된 모순 하나다.
Loading
Loading