# task-3006 — 확장 파일럿 빌드 결함 + 거짓 성공 문구 제거 (복합설계 저장 4주 중단 사고 재발방지)

**레벨**: Lv.2 · **팀**: dev4-team (비슈누) · **회장 승인 완료** (2026-08-25)

## 사고 경위 (2026-08-25 ANU 실측 · 회장 직접 제보)

회장이 오마이매니저에서 복합설계를 캡처하고 저장 버튼을 눌렀는데
**"저장됐어요" 메시지는 뜨는데 인슈로에서 조회가 안 되는** 상태가 **약 4주간(7/30~8/25)** 지속됐다.

```
consultation_history_v1  마지막 저장 = 2026-07-30 09:28   이후 25일간 유입 0
백엔드 로그(8/20~)       POST 0건 · GET 만 200 OK
```

**근인 3개가 겹쳤다:**

### ① 파일럿 빌드가 애초에 설치 불가능했다 (핵심)
```
extension/background/ingest.js:38  const INSURO_CONSULTATION_HISTORY_V1_ENABLED = false;
  → 소스 기본값 false. 서버 전송이 flag 로 차단된다(의도된 설계).
scripts/build-pilot-extension.mjs
  → 복사본에서 false→true 치환해 "파일럿 빌드" 생성. 파일럿 사용자는 이 빌드를 써야 한다.
```
그런데 이 스크립트는 `extension/` **전체를 그대로 복사**한다. 거기에 `__tests__/` 가 포함된다.
Chrome/Edge 는 `_` 로 시작하는 파일·디렉토리를 **예약어로 거부**한다:
> `Cannot load extension with file or directory name __tests__.`

★ **즉 이 스크립트 산출물은 브라우저에 로드되지 않는다.** ANU 가 2026-08-25 재현 확인했다.
`dist-pilot/` 최종 생성 시각은 2026-08-03 00:31 이었고, 그 빌드도 같은 결함을 가졌을 것이다.

### ② 확장을 고칠 때 파일럿 빌드 재생성이 자동화돼 있지 않다
2026-08-01/03 에 확장이 수정됐다(task-2880 생보 캡처, task-2882, task-2883 session storage 승격).
그러나 파일럿 빌드를 다시 만들어 회장에게 전달하는 절차가 **수동이고 아무도 챙기지 않았다.**
`.github/workflows/extension-release.yml` 에도 flag 주입·파일럿 산출 단계가 **없다.**

→ **다른 기능을 고치는 동안 되던 기능이 사용자에게서 끊겼다.** 회장 지적: *"원래 되던 기능이
다른 기능 수정할 때 영향을 받으면 안 되는 거 아닌가."* 맞는 지적이다.

### ③ 거짓 성공 문구가 4주간 사고를 숨겼다 (가장 나쁜 부분)
```javascript
// background/ingest.js:339
if (flagEnabled !== true) return { ok: false, code: "FLAG_DISABLED_STORED" };   // 전송 안 함

// content.js:2467
case "FLAG_DISABLED_STORED":
  return "저장됐어요.";        ← ok:false 인데 성공 문구
```
전송이 차단된 상태인데 사용자에게 **"저장됐어요"** 라고 알린다.
회장은 저장된 줄 알고 4주를 보냈고, 조회가 안 되는 것을 이상하게 여겨 제보하고서야 드러났다.

★ ANU 조치(2026-08-25): `__tests__` 를 제외한 zip 을 수동 생성해 전달 → 설치 후 **정상 복구 확인**
(POST 5건 · `consultation_history_v1` 6→7행 · `status=OK`).
**이 태스크는 그 수동 조치를 코드로 고정하는 것이다.**

---

## 고칠 것

### ① `scripts/build-pilot-extension.mjs` — 설치 가능한 산출물을 만들어라
- **`_` 로 시작하는 파일·디렉토리를 제외**한다(`__tests__` 등). Chrome/Edge 예약 규칙이다
- 산출물을 **바로 설치 가능한 zip** 으로도 만든다. `manifest.json` 이 **zip 최상위**에 오게 하라
  (한 겹 더 감싸면 "매니페스트 파일이 없거나 읽을 수 없습니다" 로 로드 실패한다 — ANU 실증)
- **검증 단계를 넣어라**: 산출물에 `_` 시작 항목이 0건이고 루트에 `manifest.json` 이 있는지
  스크립트가 스스로 확인하고, 아니면 **fail-closed 로 종료**하라
- flag 치환은 현행대로 유지(복사본만 true, **소스 원본은 false 불변**)

### ② 거짓 성공 문구 제거
`content.js` 의 `FLAG_DISABLED_STORED` → `"저장됐어요."` 를 **실패로 정직하게 알리는 문구**로 바꾼다.
- 사용자가 "안 됐다"는 것을 알 수 있어야 한다. 기술용어·에러코드는 노출하지 마라
- ★ 문구는 봇이 작성하되 **사실과 다른 주장을 넣지 마라.** 확신이 없으면 ANU 에 보고하라
- 같은 매핑 테이블의 다른 코드들도 훑어, **실패인데 성공처럼 읽히는 문구가 더 있는지** 점검하고 보고하라

### ③ 회귀 테스트로 봉인
- 파일럿 빌드 산출물에 `_` 시작 항목이 없음을 검증하는 테스트
- `ok:false` 인 응답 코드가 성공으로 읽히는 문구에 매핑되지 않음을 검증하는 테스트
- ★ **변이 테스트로 이빨을 증명하라**: `__tests__` 제외 로직을 되돌렸을 때, 그리고
  `FLAG_DISABLED_STORED` 문구를 "저장됐어요"로 되돌렸을 때 **각각 FAIL 하는지** 확인하고 복원하라

---

## ★ 금지 (엄수)
- **flag 를 소스에서 `true` 로 바꾸기** — `INSURO_CONSULTATION_HISTORY_V1_ENABLED = false` 는 불변.
  파일럿 빌드 스크립트만 복사본에서 치환한다
- **자동 전송 부활** — 캡처 직후 자동 POST 는 회장 결정 #4 로 차단됐다(task-2354/2800).
  전송은 오직 사용자의 "인슈로에 동의하고 저장" 버튼 클릭으로만 발생한다
- **consent 게이트 우회·완화** — `consent !== true` 차단은 감사 무결성 요건이다
- `background/ingest.js` 의 **egress choke point(`sendIngest`) 구조 변경** — 서버로 나가는 fetch 는
  이 함수 단 하나여야 한다
- URL allowlist 완화 (`api.insuro.biz` / `localhost:8000` 외 추가 금지)
- `server/**` · `src/**` · `supabase/**` 수정

## allowed_resources

```yaml
allowed_resources:
  paths:
    - "scripts/build-pilot-extension.mjs"
    - "extension/content.js"
    - "extension/__tests__/**"
    - "scripts/__tests__/**"
    - ".github/workflows/extension-release.yml"
    - "memory/reports/task-3006.md"
  forbidden_paths:
    - "extension/background/ingest.js"
    - "extension/background.js"
    - "extension/manifest.json"
    - "server/**"
    - "src/**"
    - "supabase/**"
    - "ops/**"
  commands:
    - "node"
    - "npx vitest run"
    - "npm run build"
  merge_policy: "tiered"
  ttl_hours: 48
```
★ `background/ingest.js` 를 forbidden 에 둔 이유: flag 상수와 egress choke 가 거기 있다.
문구 수정은 `content.js` 에서만 한다. `ingest.js` 변경이 꼭 필요하면 **먼저 ANU 에 보고**하라.

## 검증
1. **재현 먼저** — 현행 스크립트로 빌드한 산출물이 `__tests__` 를 포함하는지 확인해 기록하라
2. **수정 후** — 산출물에 `_` 시작 항목 0건, `manifest.json` 루트 존재를 실측 수치로 제시하라
3. **실제 로드 확인** — 가능하면 브라우저에 실제로 로드해 성공을 확인하라.
   불가하면 그 사실을 명시하고 정적 검증 결과로 대체하라 (**"로드했다"고 추정으로 쓰지 마라**)
4. **문구 대조표** — 응답 코드 → 문구 매핑을 `ok` 값과 함께 표로 제시하라
   (`ok:false` 인데 성공처럼 읽히는 항목이 남아 있는지 한눈에 보이게)
5. **변이로 봉인 실증** (위 ③)
6. 기존 확장 테스트 회귀 유지 (**base 재측정 기준선**. 명세 수치 아님)

## 운영 계약
- `origin/main`(=`a0bf0c9`) 기준 `git pull --ff-only` 후 시작
- ★ `gh` 호출 시 `GH_TOKEN="$BOT_GITHUB_TOKEN"` 주입 필수 (회장 개인 PAT 금지 — 감사기록 오염)
- 워크플로우 `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` · QC `/home/jay/workspace/teams/shared/QC-RULES.md`
- `WORKSPACE_ROOT=/home/jay/workspace` · `CHAT_ID=6937032012` · 수집자 key `ANU_KEY=c119085addb0f8b7`
- 완료 경로는 `finish-task.sh` 실행이 유일하다. 수동 `.done` 금지.
- **명세와 실측이 다르면 임의 판단하지 말고 ANU 에 보고하라.**

## 완료 조건
빌드 스크립트가 설치 가능한 산출물 생성(`_` 제외 + 루트 manifest + 자체 검증 fail-closed) ·
거짓 성공 문구 제거 · 응답코드↔문구 대조표 · 변이로 봉인 실증 · 회귀 유지

## 보고
**★★★ PR 생성까지가 범위다. 머지는 ANU 가 한다. 절대 직접 머지하지 마라.**
`memory/reports/task-3006.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.