feat: openai-compatible output validation filters - roadmap update and task implementation
This commit is contained in:
parent
772f63f25c
commit
63a6b622ce
12 changed files with 2244 additions and 7 deletions
|
|
@ -33,7 +33,7 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
|
|||
- 경로: [stream-evidence-gate-core](../../archive/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
|
||||
- 요약: codec의 response-start/event를 첫 safe release까지 stage하고 500-rune rolling, bounded terminal/fragment hold, pre-read 기본값/절대 상한 16 MiB raw-canonical ingress snapshot과 request-snapshot 기반 Filter Registry를 제공한다. Gate Coordinator가 single-flight all-complete evaluation/commit을, RecoveryPlan Coordinator와 host adapter가 strategy별 budget과 최초 실행 제외 기본값/절대 상한 3회의 request 전체 cap 아래 abort·optional one-shot plan prepare·lossless rebuild·cycle별 single re-admission을 담당한다.
|
||||
|
||||
- [계획] OpenAI-compatible 출력 검증 필터
|
||||
- [진행중] OpenAI-compatible 출력 검증 필터
|
||||
- 경로: [openai-compatible-output-validation-filters](milestones/openai-compatible-output-validation-filters.md)
|
||||
- 요약: 실제 의미 필터 전에 local/dev deterministic diagnostic mock으로 실제 codec/Core/Arbiter/recovery/ReleaseSink의 pass·observe-only·blocking recovery와 raw-free timeline을 관측하는 smoke를 선행한다. 이후 OpenAI-compatible Chat Completions와 Responses provider stream의 반복, assistant-history anchor, 동일 tool/action, schema/provider error를 caller-neutral하게 판정하는 Core `Filter` 구현체를 제공한다. filter는 model/provider별 on/off와 semantic decision/RecoveryIntent만 소유하고, 병렬 평가·all-complete arbitration·retry budget·request rebuild/re-admission은 Stream Evidence Gate Core의 공통 Coordinator를 소비한다.
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ OpenAI-compatible Chat Completions와 Responses provider 경로에서 모델 출
|
|||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
[진행중]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
|
|
@ -64,11 +64,11 @@ OpenAI-compatible Chat Completions와 Responses provider 경로에서 모델 출
|
|||
|
||||
OpenAI-compatible 출력 필터의 계약, 공통 pipeline, endpoint codec, Stream Evidence Gate 채택과 적용 정책 기반을 묶는다.
|
||||
|
||||
- [ ] [contract-doc] OpenAI-compatible 계약 문서와 구현 타입이 `/v1/chat/completions`와 `/v1/responses`의 `metadata.scheme`, 내부 `contract_schema`/`passthrough_guarded`/provider-error-retry path, provider-pool tunnel/normalized RunEvent 실행 경계, caller-neutral 반복 guard 입력, bounded raw-canonical request snapshot과 conversation identity 부재 시 degraded behavior를 설명한다. initial ingress overflow는 HTTP 413 `invalid_request_error`, internal rebuild overflow는 commit state에 맞는 recovery terminal error로 구분한다. raw tunnel provider를 normalized 실행으로 강제하지 않지만 두 path는 codec 뒤 같은 Core event/recovery 계약을 사용한다. 구현 시 [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)와 [edge-config-runtime-refresh.md](../../../../agent-contract/inner/edge-config-runtime-refresh.md)를 코드/Go handler·config type 테스트와 함께 갱신하고 caller 제품명을 runtime 조건으로 사용하지 않는다.
|
||||
- [ ] [filter-pipeline] repeat/schema/provider-error filter가 Core Go `Filter` interface로 등록된다. trigger-ready filter는 동일 immutable batch에서 병렬 평가되고 terminal trigger 미충족 schema는 blocking deferred, error event가 없는 provider-error는 nonblocking not-applicable outcome으로 complete set에 포함된다. repeat는 rolling, schema는 bounded terminal, provider-error는 hold 없는 event subscription을 선언하고 enforcement/failure mode는 Registry가 소유한다. 검증: `go test ./apps/edge/internal/openai -count=1`에서 evaluated/deferred/not-applicable 구분, no deferred-pass, all-complete/failure policy와 direct-submit 금지가 통과한다.
|
||||
- [ ] [stream-gate-adoption] Chat Completions와 Responses codec이 response status/header/opening event까지 normalized event로 만들고, Edge adapter가 Core `AttemptDispatcher`/`AttemptController`/`ReleaseSink`를 구현한다. 두 handler의 unbounded `io.ReadAll` 앞에 host pre-read limit를 적용하고 limit+1 overflow를 413으로 구분한다. SnapshotBuilder/Rebuilder는 typed view 추가 직후와 rebuild 임시 output 할당 전후의 current peak retained bytes를 다시 확인하고 종료/cancel/overflow에서 object를 release한다. provider response-start와 normalized live SSE role chunk는 첫 safe release 전 commit/flush하지 않으며 hop-by-hop/변환 전 `Content-Length`는 전달하지 않는다. Core가 rolling/terminal/fragment hold, all-complete, idle no-release와 single action을 소유한다. 검증: Chat/Responses raw-body limit-1/limit/limit+1, exact-limit body+typed-view overflow, rebuild 전후 peak/no full-read overflow/release, tunnel/normalized 두 path의 eager header/role 없음, header allowlist/content-length 제거, staged 500 retry, backpressure, path switch와 single terminal fixture가 통과한다.
|
||||
- [ ] [responses-codec] `/v1/responses`의 input item history와 response-start/text/reasoning/function-call/terminal event를 normalized event와 repair input으로 변환하고, Core의 기본값/절대 상한 16 MiB `max_ingress_snapshot_bytes` 안에서 raw body 하나를 canonical source로 unknown caller item/field를 보존하는 bounded lossless Responses `RequestRebuilder`를 제공한다. raw parser와 serializer는 Chat과 분리하고 concrete model/auth rewrite는 dispatcher admission에 둔다. 검증: Responses raw body round-trip/unknown field, stream item split, staged opening event, reasoning/encrypted reasoning 보존, function-call, terminal/error, path-switch recovery가 Chat과 같은 semantic decision/plan을 내고 endpoint shape를 유지하며 retained/rebuild limit 초과는 no-dispatch로 끝난다.
|
||||
- [ ] [filter-policy] filter enable/disable, `blocking|observe_only`, hold mode/bound를 environment(`dev`, `dev-corp`)와 model group/model/provider/protocol capability별로 평가한다. request 시작 시 config generation과 required capability를 고정해 schema 같은 필수 filter를 지원하지 않는 provider 후보는 admission 전에 제외하고, actual target별 active set은 attempt마다 같은 snapshot으로 다시 resolve한다. 검증: Chat/Responses와 qwen/gemma/ornith fixture에서 policy precedence, mid-request reload 격리, provider 전환 re-resolution, required no-candidate 400, disabled/duplicate filter 미평가와 caller-neutral 분기가 통과한다.
|
||||
- [x] [contract-doc] OpenAI-compatible 계약 문서와 구현 타입이 `/v1/chat/completions`와 `/v1/responses`의 `metadata.scheme`, 내부 `contract_schema`/`passthrough_guarded`/provider-error-retry path, provider-pool tunnel/normalized RunEvent 실행 경계, caller-neutral 반복 guard 입력, bounded raw-canonical request snapshot과 conversation identity 부재 시 degraded behavior를 설명한다. initial ingress overflow는 HTTP 413 `invalid_request_error`, internal rebuild overflow는 commit state에 맞는 recovery terminal error로 구분한다. raw tunnel provider를 normalized 실행으로 강제하지 않지만 두 path는 codec 뒤 같은 Core event/recovery 계약을 사용한다. 구현 시 [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)와 [edge-config-runtime-refresh.md](../../../../agent-contract/inner/edge-config-runtime-refresh.md)를 코드/Go handler·config type 테스트와 함께 갱신하고 caller 제품명을 runtime 조건으로 사용하지 않는다.
|
||||
- [x] [filter-pipeline] repeat/schema/provider-error filter가 Core Go `Filter` interface로 등록된다. trigger-ready filter는 동일 immutable batch에서 병렬 평가되고 terminal trigger 미충족 schema는 blocking deferred, error event가 없는 provider-error는 nonblocking not-applicable outcome으로 complete set에 포함된다. repeat는 rolling, schema는 bounded terminal, provider-error는 hold 없는 event subscription을 선언하고 enforcement/failure mode는 Registry가 소유한다. 검증: `go test ./apps/edge/internal/openai -count=1`에서 evaluated/deferred/not-applicable 구분, no deferred-pass, all-complete/failure policy와 direct-submit 금지가 통과한다.
|
||||
- [x] [stream-gate-adoption] Chat Completions와 Responses codec이 response status/header/opening event까지 normalized event로 만들고, Edge adapter가 Core `AttemptDispatcher`/`AttemptController`/`ReleaseSink`를 구현한다. 두 handler의 unbounded `io.ReadAll` 앞에 host pre-read limit를 적용하고 limit+1 overflow를 413으로 구분한다. SnapshotBuilder/Rebuilder는 typed view 추가 직후와 rebuild 임시 output 할당 전후의 current peak retained bytes를 다시 확인하고 종료/cancel/overflow에서 object를 release한다. provider response-start와 normalized live SSE role chunk는 첫 safe release 전 commit/flush하지 않으며 hop-by-hop/변환 전 `Content-Length`는 전달하지 않는다. Core가 rolling/terminal/fragment hold, all-complete, idle no-release와 single action을 소유한다. 검증: Chat/Responses raw-body limit-1/limit/limit+1, exact-limit body+typed-view overflow, rebuild 전후 peak/no full-read overflow/release, tunnel/normalized 두 path의 eager header/role 없음, header allowlist/content-length 제거, staged 500 retry, backpressure, path switch와 single terminal fixture가 통과한다.
|
||||
- [x] [responses-codec] `/v1/responses`의 input item history와 response-start/text/reasoning/function-call/terminal event를 normalized event와 repair input으로 변환하고, Core의 기본값/절대 상한 16 MiB `max_ingress_snapshot_bytes` 안에서 raw body 하나를 canonical source로 unknown caller item/field를 보존하는 bounded lossless Responses `RequestRebuilder`를 제공한다. raw parser와 serializer는 Chat과 분리하고 concrete model/auth rewrite는 dispatcher admission에 둔다. 검증: Responses raw body round-trip/unknown field, stream item split, staged opening event, reasoning/encrypted reasoning 보존, function-call, terminal/error, path-switch recovery가 Chat과 같은 semantic decision/plan을 내고 endpoint shape를 유지하며 retained/rebuild limit 초과는 no-dispatch로 끝난다.
|
||||
- [x] [filter-policy] filter enable/disable, `blocking|observe_only`, hold mode/bound를 environment(`dev`, `dev-corp`)와 model group/model/provider/protocol capability별로 평가한다. request 시작 시 config generation과 required capability를 고정해 schema 같은 필수 filter를 지원하지 않는 provider 후보는 admission 전에 제외하고, actual target별 active set은 attempt마다 같은 snapshot으로 다시 resolve한다. 검증: Chat/Responses와 qwen/gemma/ornith fixture에서 policy precedence, mid-request reload 격리, provider 전환 re-resolution, required no-candidate 400, disabled/duplicate filter 미평가와 caller-neutral 분기가 통과한다.
|
||||
|
||||
### Epic: [output-filter-recovery] Output Filter Recovery and Evidence
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/01_resume_notice_builder plan=0 tag=OFR-RESUME -->
|
||||
|
||||
# Code Review Reference - OFR-RESUME
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-28
|
||||
task=m-openai-compatible-output-validation-filters/01_resume_notice_builder, plan=0, tag=OFR-RESUME
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `resume-notice-builder`: content/reasoning 원문과 고정 영어 지시문으로 endpoint별 continuation 요청 조립
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정과 `review_rework_count` / `evidence_integrity_failure` 라우팅 신호를 append한다.
|
||||
2. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_0.log`, `PLAN-cloud-G08.md` → `plan_cloud_G08_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/01_resume_notice_builder/`로 이동한다. WARN/FAIL이면 code-review skill이 요구하는 다음 filesystem state를 완전히 작성한다.
|
||||
4. PASS이고 task group이 `m-openai-compatible-output-validation-filters`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| OFR-RESUME-1 request-local recovery source와 Rebuilder 조립 | [ ] |
|
||||
| OFR-RESUME-2 runtime ownership과 문맥 한도 fail-closed | [ ] |
|
||||
| OFR-RESUME-3 계약/spec 동기화와 패킷 검증 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-RESUME-1] request-local content/reasoning recovery source와 고정 resume directive 조립을 구현하고 lifecycle/원문 보존 테스트를 추가한다.
|
||||
- [ ] [OFR-RESUME-2] Chat/Responses runtime에 recorder와 endpoint별 Rebuilder를 연결하고 abort-before-build, context overflow no-dispatch, actual-dispatch-only budget 경계를 검증한다.
|
||||
- [ ] [OFR-RESUME-3] outer/inner 계약과 현재 구현 spec을 S20 동작으로 갱신하고 local fresh 검증을 통과한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정과 검증된 `review_rework_count`, `evidence_integrity_failure`를 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_0.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G08_0.log`로 아카이브한다.
|
||||
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리 `agent-task/m-openai-compatible-output-validation-filters/01_resume_notice_builder/`를 `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/01_resume_notice_builder/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-openai-compatible-output-validation-filters`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent를 제거하거나, 남은 sibling이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이면 code-review skill의 판정에 맞는 다음 filesystem state를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- continuation source가 request-local이며 filter/로그/cross-request cache에 raw model output을 노출하지 않는가.
|
||||
- fixed English directive가 byte-for-byte 일치하고 caller user messages/input/instructions가 rebuilt body에 없는가.
|
||||
- content와 think/reasoning provenance가 보존되고 cursor 외 요약·절단·재작성이 없는가.
|
||||
- all-complete plan 선택과 abort 뒤에만 rebuild하며 overflow/unknown context에서는 dispatch와 budget 소비가 0회인가.
|
||||
- Chat/Responses shape, single terminal, nil `RecoveryPlanPreparer`가 회귀 테스트로 고정되는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
```
|
||||
|
||||
_구현 에이전트가 실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
### Targeted
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestOpenAI(RequestRebuilderBuilds(Chat|Responses)RepeatResume|RecoverySourceStoreLifecycle|RepeatResume)'
|
||||
```
|
||||
|
||||
_구현 에이전트가 실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
### Package/contract
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
_구현 에이전트가 실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## 섹션 소유권
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Agent UI Completion | Mixed | Present only for plan-required agent-ui code work; implementing agent fills actual evidence, review agent applies `구현됨` status/evidence update on PASS and copies the section into `complete.log` |
|
||||
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/01_resume_notice_builder plan=0 tag=OFR-RESUME -->
|
||||
|
||||
# Output Filter Recovery: endpoint별 재개 요청 조립
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료 전 `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 반드시 채운다. 검증 명령을 실행하고 active 파일을 그대로 둔 채 review-ready 상태를 보고한다. 종결, 로그 archive, `complete.log` 작성은 code-review skill 전용이다. 막히면 구현 소유 evidence에 정확한 blocker, 시도한 명령/출력, 재개 조건만 기록하며 사용자 질문, user-input 도구, control-plane stop 파일 생성, 다음 상태 분류를 하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
현재 `RequestRebuilder`는 continuation directive를 외부 patch store 값으로 치환하지만, 중단된 모델의 content와 reasoning을 request-local하게 보존하고 endpoint별 복구 요청으로 조립하는 계약은 없다. `resume-notice-builder`는 후속 repeat guard가 순수 filter로 남을 수 있도록 모델 출력 snapshot과 고정 영어 재개 지시문 조립 책임을 Rebuilder에 고정한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `resume-notice-builder`: content/reasoning 원문과 고정 영어 지시문으로 endpoint별 continuation 요청 조립
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- 규칙/라우팅: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`
|
||||
- 도메인/테스트 규칙: `agent-ops/rules/project/domains/edge.md`, `agent-ops/rules/project/domains/platform-common.md`, `agent-ops/rules/project/domains/testing.md`, `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`, `agent-test/dev/rules.md`, `agent-test/dev/edge-smoke.md`, `agent-test/dev/platform-common-smoke.md`, `agent-test/dev/testing-smoke.md`
|
||||
- 로드맵/설계/계약: `agent-roadmap/ROADMAP.md`, `agent-roadmap/current.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, `agent-contract/index.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/stream-evidence-gate.md`, `agent-spec/input/openai-compatible-surface.md`, `agent-spec/runtime/provider-pool-config-refresh.md`
|
||||
- 소스: `apps/edge/internal/openai/openai_request_rebuilder.go`, `apps/edge/internal/openai/stream_gate_runtime.go`, `apps/edge/internal/openai/stream_gate_tunnel_codec.go`, `apps/edge/internal/openai/chat_types.go`, `apps/edge/internal/openai/responses_types.go`, `apps/edge/internal/openai/chat_handler.go`, `apps/edge/internal/openai/responses_handler.go`, `packages/go/streamgate/filter_contract.go`, `packages/go/streamgate/event.go`, `go.mod`
|
||||
- 테스트: `apps/edge/internal/openai/stream_gate_pipeline_test.go`, `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`, `apps/edge/internal/openai/responses_handler_test.go`, `packages/go/streamgate/event_test.go`
|
||||
|
||||
### SDD 기준
|
||||
|
||||
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, 상태 `[승인됨]`, SDD 잠금 해제.
|
||||
- 대상: S20 → Milestone Task `resume-notice-builder`.
|
||||
- Evidence Map: S20의 all-complete/abort-before-build, content·think/reasoning channel provenance, 고정 지시문, caller message 제외, 문맥 초과 no-dispatch, Chat/Responses endpoint shape fixture를 구현 체크리스트와 최종 검증에 그대로 반영한다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- 기본 `test_env=local`. `agent-test/local/rules.md`와 edge/platform-common/testing smoke profile을 읽었고 fresh Go test는 `-count=1`, cache 결과는 evidence로 인정하지 않는다.
|
||||
- 이 패킷은 외부 provider 재현이 아닌 결정론적 Rebuilder 계약으로 독립 PASS할 수 있어 dev 실행을 요구하지 않는다. 다만 전체 Milestone의 후속 live evidence를 확인하기 위해 dev 규칙/profile도 읽었으며 이 패킷에는 적용하지 않는다.
|
||||
- setup 기준은 `go version && go env GOMOD`, 전체 정적 확인은 `git diff --check`, repository fallback은 `make test`다. 누락 또는 `<확인 필요>` 값은 없다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 `openAIRequestRebuilder` 테스트는 exact/continuation/schema patch와 unknown-field 보존을 다루지만 고정 resume notice와 caller history 제거를 다루지 않는다: 회귀 fixture 추가 필요.
|
||||
- Chat/Responses Core vertical slice는 recovery ordering을 다루지만 source snapshot이 abort 뒤에만 소비되는지와 context overflow no-dispatch를 다루지 않는다: spy dispatcher/controller fixture 추가 필요.
|
||||
- translator/local model/`RecoveryPlanPreparer` 미호출은 현재 구현에 해당 경로가 없으므로 nil preparer와 호출 횟수 0을 명시적으로 고정하는 테스트가 필요하다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- rename/remove 없음.
|
||||
- 변경 대상 호출점: `newOpenAIRequestRebuilder`는 `stream_gate_runtime.go:861`, `stream_gate_runtime.go:1299`에서 호출된다. 새 source recorder는 Chat/Responses normalized event source factory 두 곳에만 결합한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- 전체 `output-filter-recovery`는 다섯 개의 안정 계약으로 분리한다: resume builder, repeat guard, provider error retry, schema contract, ops evidence.
|
||||
- 이 하위 작업의 독립 PASS 계약은 “선택된 continuation plan만 request-local source snapshot을 endpoint shape로 조립하며, caller history 및 추가 모델 호출 없이 overflow 시 dispatch를 막는다”이다.
|
||||
- 선행 dependency가 없는 `01_resume_notice_builder`이며, 후속 `02+01_repeat_guard`가 이 작업의 `complete.log`를 요구한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 반복 감지/fingerprint/온도 후보 선택은 `repeat-guard` 패킷으로 제외한다.
|
||||
- provider error matcher와 schema validation은 각각 후속 패킷으로 제외한다.
|
||||
- managed `length` continuation, Claude codec, 번역기, 로컬 모델, `RecoveryPlanPreparer` 구현은 승인 SDD 범위 밖이라 제외한다.
|
||||
- 새로운 외부 Go dependency는 필요하지 않으며 `go.mod`를 변경하지 않는다.
|
||||
|
||||
### 최종 라우팅
|
||||
|
||||
- evaluation_mode: `first-pass`; finalizer: `finalize-task-policy.sh pair`.
|
||||
- build closure: scope=2, state=2, blast=1, evidence=1, verification=2 → G08; base=`local-fit`, final route=`risk-boundary`, lane=`cloud`, filename=`PLAN-cloud-G08.md`.
|
||||
- review closure: scope=2, state=2, blast=1, evidence=1, verification=2 → G08; route=`official-review`, lane=`cloud`, filename=`CODE_REVIEW-cloud-G08.md`.
|
||||
- `large_indivisible_context=false`.
|
||||
- matched loop-risk signatures: `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product` (4).
|
||||
- recovery signals: `review_rework_count=0`, `evidence_integrity_failure=false`.
|
||||
- capability gap: 없음.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-RESUME-1] request-local content/reasoning recovery source와 고정 resume directive 조립을 구현하고 lifecycle/원문 보존 테스트를 추가한다.
|
||||
- [ ] [OFR-RESUME-2] Chat/Responses runtime에 recorder와 endpoint별 Rebuilder를 연결하고 abort-before-build, context overflow no-dispatch, actual-dispatch-only budget 경계를 검증한다.
|
||||
- [ ] [OFR-RESUME-3] outer/inner 계약과 현재 구현 spec을 S20 동작으로 갱신하고 local fresh 검증을 통과한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
### [OFR-RESUME-1] request-local recovery source와 Rebuilder 조립
|
||||
|
||||
#### 문제
|
||||
|
||||
- `openai_request_rebuilder.go:333-344`는 ingress, patch store, rebuilt store만 보유한다.
|
||||
- `openai_request_rebuilder.go:439-457`의 continuation은 filter 외부에서 미리 넣은 임의 patch를 소비해, S20의 content/reasoning provenance와 caller-message 제거를 강제할 수 없다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`NormalizedEventSource` 앞의 request-local recorder가 text/reasoning delta를 event order와 channel별로 bounded 보존하고, filter에는 raw 값이 아닌 stable snapshot ref/cursor만 제공한다. Rebuilder는 selected continuation directive를 받은 뒤 recorder snapshot에서 cursor 이전 원문을 읽어 다음 고정 문구와 함께 endpoint별 body를 직접 만든다.
|
||||
|
||||
Before (`apps/edge/internal/openai/openai_request_rebuilder.go:333`):
|
||||
|
||||
```go
|
||||
type openAIRequestRebuilder struct {
|
||||
ingress *openAIIngressSnapshot
|
||||
endpoint string
|
||||
patches *openAIRecoveryPatchStore
|
||||
rebuilt *openAIRebuiltRequestStore
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
const openAIRepeatResumeDirective = "The previous model output was stopped after a repetition loop was detected. Continue from the provided content and reasoning output without repeating already generated text."
|
||||
|
||||
type openAIRequestRebuilder struct {
|
||||
ingress *openAIIngressSnapshot
|
||||
endpoint string
|
||||
recoverySource *openAIRecoverySourceStore
|
||||
patches *openAIRecoveryPatchStore
|
||||
rebuilt *openAIRebuiltRequestStore
|
||||
}
|
||||
```
|
||||
|
||||
Chat rebuild는 original `messages`를 복사하지 않고 assistant content/reasoning provenance와 private fixed instruction만 직렬화한다. Responses rebuild는 original `input`/`instructions`를 제거하고 endpoint-native assistant item/reasoning representation과 fixed instruction을 사용한다. 원문은 반복 구간 이후를 제외하는 cursor 외에는 요약·절단·재작성하지 않는다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/openai_request_rebuilder.go`: recovery source store, stable ref/cursor lookup, fixed directive, Chat/Responses continuation body, close/lease lifecycle.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: exact directive, channel provenance, caller history 부재, no-summary/no-truncation/no-rewrite, one-shot snapshot consumption.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestOpenAIRequestRebuilderBuildsChatRepeatResume`, `TestOpenAIRequestRebuilderBuildsResponsesRepeatResume`, `TestOpenAIRecoverySourceStoreLifecycle`.
|
||||
- fixture는 content와 reasoning/think에 서로 다른 sentinel을 넣고 caller user sentinel이 rebuilt JSON에 없으며 model output sentinel은 byte-for-byte 유지되는지 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestOpenAI(RequestRebuilderBuilds(Chat|Responses)RepeatResume|RecoverySourceStoreLifecycle)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, fixed directive exact match, caller sentinel 0건.
|
||||
|
||||
### [OFR-RESUME-2] runtime ownership과 문맥 한도 fail-closed
|
||||
|
||||
#### 문제
|
||||
|
||||
- `stream_gate_runtime.go:861`, `stream_gate_runtime.go:1299`는 Rebuilder를 ingress/endpoint만으로 만들고 attempt event source와 recovery source를 공유하지 않는다.
|
||||
- 현재 nil `RecoveryPlanPreparer` 경계는 유지되지만, 문맥 한도 검사가 없어 oversized content/reasoning이 recovery dispatch까지 갈 수 있다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
각 request runtime에서 recorder를 한 번 만들고 initial/recovery event source를 같은 recorder wrapper로 감싼다. Rebuilder는 actual target의 model catalog context window와 기존 prompt estimator를 주입받아 완성된 resume body의 prompt tokens를 측정한다. context window가 없거나 `rebuilt_prompt_tokens + reserve`가 limit을 넘으면 draft를 만들지 않고 typed rebuild error를 반환해 dispatcher 호출과 recovery budget 소비를 모두 막는다.
|
||||
|
||||
Before (`apps/edge/internal/openai/stream_gate_runtime.go:861`):
|
||||
|
||||
```go
|
||||
rebuilder, err := newOpenAIRequestRebuilder(dc.ingress, openAIRebuildEndpointChat)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
recoverySource := newOpenAIRecoverySourceStore(dc.ingress)
|
||||
rebuilder, err := newOpenAIRequestRebuilder(
|
||||
dc.ingress, openAIRebuildEndpointChat, recoverySource, contextLimitFor(dc.dispatch),
|
||||
)
|
||||
```
|
||||
|
||||
Budget은 Core가 outbound dispatch를 실제 시작할 때만 소비한다. recorder/Rebuilder 실패에는 admission/submit이 0회여야 하며 preparer는 계속 nil이다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_runtime.go`: request-local recorder 공유, model context bound 전달, nil preparer 유지.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: all-complete → abort → rebuild → dispatch 순서, overflow no-dispatch/no-budget, Chat/Responses shape.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestOpenAIRepeatResumeBuildRunsAfterAbort`, `TestOpenAIRepeatResumeContextOverflowDoesNotDispatch`, `TestOpenAIRepeatResumeDoesNotUsePreparer`.
|
||||
- fake controller/dispatcher call order와 dispatch count, recovery budget snapshot을 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestOpenAIRepeatResume'
|
||||
```
|
||||
|
||||
기대 결과: PASS, overflow에서 dispatch 0회, 정상 케이스에서 abort 이후 dispatch 1회.
|
||||
|
||||
### [OFR-RESUME-3] 계약/spec 동기화와 패킷 검증
|
||||
|
||||
#### 문제
|
||||
|
||||
- `agent-contract/outer/openai-compatible-api.md:94`와 `agent-spec/runtime/stream-evidence-gate.md:99`는 semantic filter를 foundation으로 설명하고 S20 resume builder 계약을 아직 현재 동작으로 기록하지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
S20의 endpoint별 shape, 고정 directive, caller history 제외, context overflow no-dispatch, no translator/local model/preparer, actual outbound dispatch-only budget을 outer/inner contract와 현재 구현 spec에 반영한다. foundation 설명은 repeat 감지 자체가 아직 후속 작업임을 유지하면서 builder가 제공하는 seam만 현재 구현으로 좁혀 쓴다.
|
||||
|
||||
Before (`agent-contract/outer/openai-compatible-api.md:94`):
|
||||
|
||||
```text
|
||||
foundation 단계의 repeat_guard/schema_gate/provider_error는 ... 후속 Task 전까지 활성 동작으로 간주하지 않는다.
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```text
|
||||
continuation plan이 선택되면 endpoint Rebuilder는 request-local 모델 출력과 고정 영어 지시문만 사용하며 caller history를 복사하지 않는다. 문맥 한도 초과는 dispatch 전 fail-closed다.
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: caller-visible Chat/Responses continuation 계약.
|
||||
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`: request-start context snapshot 및 restart-required 경계 유지.
|
||||
- [ ] `agent-spec/runtime/stream-evidence-gate.md`: recovery source/Rebuilder lifecycle.
|
||||
- [ ] `agent-spec/input/openai-compatible-surface.md`: endpoint shape와 unknown field 범위.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 문서 전용 테스트를 새로 만들지 않는다. OFR-RESUME-1/2의 executable fixture와 `git diff --check`가 계약 동기화 evidence다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
기대 결과: whitespace 오류 0건, 패키지 PASS.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
1. OFR-RESUME-1에서 source store와 endpoint body 계약을 먼저 고정한다.
|
||||
2. OFR-RESUME-2에서 runtime ownership/abort/dispatch 경계를 연결한다.
|
||||
3. OFR-RESUME-3에서 검증된 구현 사실만 계약/spec에 반영한다.
|
||||
|
||||
이 task directory는 독립형 `01_resume_notice_builder`이므로 선행 `complete.log` 의존성이 없다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `apps/edge/internal/openai/openai_request_rebuilder.go` | OFR-RESUME-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_runtime.go` | OFR-RESUME-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | OFR-RESUME-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_vertical_slice_test.go` | OFR-RESUME-2 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | OFR-RESUME-3 |
|
||||
| `agent-contract/inner/edge-config-runtime-refresh.md` | OFR-RESUME-3 |
|
||||
| `agent-spec/runtime/stream-evidence-gate.md` | OFR-RESUME-3 |
|
||||
| `agent-spec/input/openai-compatible-surface.md` | OFR-RESUME-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestOpenAI(RequestRebuilderBuilds(Chat|Responses)RepeatResume|RecoverySourceStoreLifecycle|RepeatResume)'
|
||||
go test -count=1 ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
기대 결과: setup이 현재 module을 가리키고, 모든 명령 PASS, cached test output은 evidence로 사용하지 않는다. S20 fixture에서 directive exact match, source provenance, caller history 부재, no rewrite, overflow no-dispatch, single terminal이 확인된다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/02+01_repeat_guard plan=0 tag=OFR-REPEAT -->
|
||||
|
||||
# Code Review Reference - OFR-REPEAT
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-28
|
||||
task=m-openai-compatible-output-validation-filters/02+01_repeat_guard, plan=0, tag=OFR-REPEAT
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `repeat-guard`: rolling content/history/action 반복 감지와 safe continuation
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정과 `review_rework_count` / `evidence_integrity_failure` 라우팅 신호를 append한다.
|
||||
2. `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_0.log`, `PLAN-cloud-G10.md` → `plan_cloud_G10_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-openai-compatible-output-validation-filters/02+01_repeat_guard/`로 이동한다. WARN/FAIL이면 code-review skill이 요구하는 다음 filesystem state를 완전히 작성한다.
|
||||
4. PASS이면 milestone 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| OFR-REPEAT-1 caller-neutral history preflight | [ ] |
|
||||
| OFR-REPEAT-2 Unicode rolling 및 action filter | [ ] |
|
||||
| OFR-REPEAT-3 continuation dispatch와 downstream 단일성 | [ ] |
|
||||
| OFR-REPEAT-4 계약 동기화와 local/dev evidence | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-REPEAT-1] Chat/Responses raw history를 role/channel/action별 bounded semantic view로 preflight하고 no-lineage/no-unsafe-mutation 규칙을 테스트한다.
|
||||
- [ ] [OFR-REPEAT-2] Unicode rolling/look-behind repeat 및 action guard를 pure filter decision/typed continuation intent로 구현한다.
|
||||
- [ ] [OFR-REPEAT-3] resume builder와 연결해 safe prefix/cursor, temperature 후보, duplicate opening/prefix 및 single terminal 경계를 검증한다.
|
||||
- [ ] [OFR-REPEAT-4] 계약/spec/config 예시를 갱신하고 결정론적 generic fixture와 dev `ornith:35b` capacity+1 × 3 smoke evidence를 완료한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정과 검증된 `review_rework_count`, `evidence_integrity_failure`를 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G10_0.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G10_0.log`로 아카이브한다.
|
||||
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리를 월별 archive로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이면 milestone 완료 이벤트 메타데이터를 보고하고 roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이면 code-review skill의 판정에 맞는 다음 filesystem state를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분과 이유를 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 실제 설계 결정을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- predecessor `01_resume_notice_builder`의 `complete.log`가 구현 시작 전에 존재했는가.
|
||||
- request 밖 TTL/session/caller state를 사용하지 않고 role/channel provenance와 missing reasoning degradation을 지키는가.
|
||||
- 500-rune default, Unicode boundary, committed look-behind, safe cursor가 byte/rune 혼동 없이 동작하는가.
|
||||
- final content/tool/signed/encrypted/unknown field를 조용히 mutate하지 않고 side-effect 뒤 repair를 금지하는가.
|
||||
- caller temperature 명시값을 보존하고 생략 때만 0.2/0.4/0.6을 적용하는가.
|
||||
- response-start/role/prefix/[DONE]/dispatch가 recovery cycle에서 중복되지 않는가.
|
||||
- live evidence의 raw prompt/SSE/output/token이 ignored path 밖이나 stdout/tracked 문서에 없는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### Dependency
|
||||
|
||||
```bash
|
||||
test -f agent-task/m-openai-compatible-output-validation-filters/01_resume_notice_builder/complete.log
|
||||
```
|
||||
|
||||
_구현 에이전트가 실제 출력과 종료 코드를 기록한다._
|
||||
|
||||
### Local
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
_구현 에이전트가 실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
### Dev preflight/smoke
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
git rev-parse HEAD
|
||||
git fetch origin
|
||||
git rev-parse origin/main
|
||||
go version && go env GOMOD
|
||||
iop-edge --help
|
||||
iop-edge version
|
||||
go test ./apps/edge/...
|
||||
iop-edge smoke openai --base-url http://127.0.0.1:18083
|
||||
IOP_OPENAI_BASE_URL=http://127.0.0.1:18083 IOP_OPENAI_MODEL=ornith:35b IOP_OPENAI_SMOKE_CONCURRENCY=5 IOP_OPENAI_SMOKE_RUNS=3 IOP_OPENAI_SMOKE_PROMPT_FILE=agent-test/runs/output-filter-recovery/repeat-prompt.txt IOP_OPENAI_SMOKE_ARTIFACT_DIR=agent-test/runs/output-filter-recovery/repeat-guard go test -count=1 -v ./apps/edge/internal/openai -run TestDevRepeatGuardOrnithSmoke
|
||||
```
|
||||
|
||||
_구현 에이전트가 runner/source/build/runtime identity와 sanitized reproduced/not_reproduced evidence를 기록하고 raw/token은 붙이지 않는다._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## 섹션 소유권
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Agent UI Completion | Mixed | Present only for plan-required agent-ui code work; not applicable here |
|
||||
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/02+01_repeat_guard plan=0 tag=OFR-REPEAT -->
|
||||
|
||||
# Output Filter Recovery: caller-neutral repeat guard
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 전에 선행 task의 `complete.log`를 확인하고, 완료 전 `CODE_REVIEW-cloud-G10.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 반드시 채운다. 검증 명령을 실행하고 active 파일을 유지한 채 review-ready 상태를 보고한다. 종결, archive, `complete.log` 작성은 code-review skill 전용이다. 막히면 정확한 blocker, 시도한 명령/출력, 재개 조건만 구현 evidence에 기록하며 사용자 질문, user-input 도구, control-plane stop 파일 생성, 다음 상태 분류를 하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
현재 `repeat_guard`는 500-rune rolling hold shape만 등록하고 모든 batch를 pass한다. 이 작업은 raw OpenAI-compatible request history와 current stream만 사용해 caller-neutral 반복을 판정하고, 안전한 content continuation 또는 side-effect-safe terminal로 수렴시키며 S03/S04/S09-S12의 결정론적 evidence를 고정한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `repeat-guard`: rolling content/history/action 반복 감지와 safe continuation
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- 규칙/테스트: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`, `agent-ops/rules/project/domains/edge.md`, `agent-ops/rules/project/domains/platform-common.md`, `agent-ops/rules/project/domains/testing.md`, `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`, `agent-test/dev/rules.md`, `agent-test/dev/edge-smoke.md`, `agent-test/dev/platform-common-smoke.md`, `agent-test/dev/testing-smoke.md`
|
||||
- 로드맵/설계/계약: `agent-roadmap/ROADMAP.md`, `agent-roadmap/current.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, `agent-contract/index.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/stream-evidence-gate.md`, `agent-spec/input/openai-compatible-surface.md`, `agent-spec/runtime/provider-pool-config-refresh.md`
|
||||
- 소스: `apps/edge/internal/openai/stream_gate_filters.go`, `apps/edge/internal/openai/stream_gate_policy.go`, `apps/edge/internal/openai/openai_request_rebuilder.go`, `apps/edge/internal/openai/stream_gate_runtime.go`, `apps/edge/internal/openai/stream_gate_tunnel_codec.go`, `apps/edge/internal/openai/chat_decode.go`, `apps/edge/internal/openai/responses_decode.go`, `apps/edge/internal/openai/chat_types.go`, `apps/edge/internal/openai/responses_types.go`, `packages/go/config/edge_types.go`, `packages/go/streamgate/filter_contract.go`, `packages/go/streamgate/event.go`, `configs/edge.yaml`, `go.mod`
|
||||
- 테스트: `apps/edge/internal/openai/stream_gate_filters_test.go`, `apps/edge/internal/openai/stream_gate_policy_test.go`, `apps/edge/internal/openai/stream_gate_pipeline_test.go`, `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`, `packages/go/config/stream_evidence_gate_config_test.go`, `packages/go/streamgate/event_test.go`
|
||||
|
||||
### SDD 기준
|
||||
|
||||
- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, `[승인됨]`, 잠금 해제.
|
||||
- 대상: S03, S04, S09, S10, S11, S12 → `repeat-guard`.
|
||||
- Evidence Map은 Korean multi-byte 6문단 repeat, 200/500-rune pending/look-behind, stream-open cursor/single `[DONE]`, tool side-effect 차단, reasoning alias/history 미전송, completed progress/no-progress, 온도 명시/생략 fixture를 요구한다. 각 항목을 구현 체크리스트와 fresh/local+dev 검증에 직접 연결한다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local + dev`. local fresh test는 `-count=1`; `git diff --check`, edge/platform-common package test, `make test`를 적용한다.
|
||||
- dev는 roadmap의 live acceptance 때문에 필수다. runner workdir=`/Users/toki/agent-work/iop-dev`, clean `origin/main` sync, 같은 source/build identity로 참여 환경 전체 rebuild/redeploy/restart가 선행되어야 한다.
|
||||
- dev preflight: branch/HEAD/dirty, `git rev-parse`, `git status --short`, source sync; `go version`, `go env GOMOD`; `iop-edge --help`, `iop-edge version`; config=`build/dev-runtime/edge.yaml`; Edge identity/port `127.0.0.1:18083`; provider host OS/arch와 `ornith:35b` route/capacity=4 확인. credential은 remote environment에서만 읽고 stdout/stderr에 출력하지 않는다.
|
||||
- mismatch가 있으면 `git fetch origin && git checkout main && git pull --ff-only origin main` 뒤 전체 build/deploy/restart를 수행하고 smoke를 재시작한다. dirty/divergent checkout에서는 live evidence를 만들지 않는다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 filter test는 registration/hold/pass foundation만 검증한다: Unicode rolling detector, committed look-behind, typed continuation intent가 없다.
|
||||
- 기존 policy test는 selector/capability만 검증한다: raw role/channel history, reasoning alias, progress/no-progress preflight가 없다.
|
||||
- vertical slice는 diagnostic filter recovery만 검증한다: repeated content의 safe prefix/cursor, duplicate opening/prefix, single `[DONE]`, tool side-effect boundary가 없다.
|
||||
- live `ornith:35b` capacity+1 × 3 evidence harness가 없다. deterministic fixture와 분리된 env-gated dev smoke를 추가해야 한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- rename/remove 없음.
|
||||
- 변경 호출점: `newOpenAIOutputFilter`는 `stream_gate_policy.go:94`에서 생성된다. `openAIOutputFilterContext`는 Chat/Responses registry 구성 호출점 전체에 전달되므로 raw history semantic view 추가 시 모든 struct literal을 compile-time로 갱신한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- 이 패킷의 안정 계약은 “request 밖 state 없이 history/current-stream 반복을 판정하고, pure filter intent + Core/Rebuilder 경계로 safe prefix continuation 또는 no-repair terminal을 만든다”이다.
|
||||
- producer `01_resume_notice_builder`가 recovery source/Rebuilder contract를 독립 PASS한 뒤 시작한다. 현재 `agent-task/m-openai-compatible-output-validation-filters/01_resume_notice_builder/complete.log`가 없으므로 predecessor는 `missing`; runtime은 완료 전 이 task를 실행하지 않는다.
|
||||
- 후속 provider/schema/ops 작업은 동일 filter/runtime 파일을 만지므로 충돌 방지를 위해 직렬화한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- cross-request TTL/session lineage와 Pi session parsing은 명시적으로 제외한다.
|
||||
- assistant final content, tool call, signed/encrypted/unknown reasoning field mutation은 제외한다.
|
||||
- provider error matcher, schema validator, managed length continuation은 후속 task로 제외한다.
|
||||
- live smoke raw SSE/output은 ignored `agent-test/runs/**`만 사용하고 tracked plan/spec에 복제하지 않는다.
|
||||
- 외부 dependency를 추가하지 않는다.
|
||||
|
||||
### 최종 라우팅
|
||||
|
||||
- evaluation_mode=`first-pass`; finalizer=`finalize-task-policy.sh pair`.
|
||||
- build closure: scope=2, state=2, blast=2, evidence=2, verification=2 → G10; base/final=`grade-boundary`, lane=`cloud`, filename=`PLAN-cloud-G10.md`.
|
||||
- review closure: scope=2, state=2, blast=2, evidence=2, verification=2 → G10; route=`official-review`, lane=`cloud`, filename=`CODE_REVIEW-cloud-G10.md`.
|
||||
- `large_indivisible_context=false`.
|
||||
- matched loop-risk signatures: `temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, `variant_product` (5).
|
||||
- recovery signals: `review_rework_count=0`, `evidence_integrity_failure=false`.
|
||||
- capability gap: 없음.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-REPEAT-1] Chat/Responses raw history를 role/channel/action별 bounded semantic view로 preflight하고 no-lineage/no-unsafe-mutation 규칙을 테스트한다.
|
||||
- [ ] [OFR-REPEAT-2] Unicode rolling/look-behind repeat 및 action guard를 pure filter decision/typed continuation intent로 구현한다.
|
||||
- [ ] [OFR-REPEAT-3] resume builder와 연결해 safe prefix/cursor, temperature 후보, duplicate opening/prefix 및 single terminal 경계를 검증한다.
|
||||
- [ ] [OFR-REPEAT-4] 계약/spec/config 예시를 갱신하고 결정론적 generic fixture와 dev `ornith:35b` capacity+1 × 3 smoke evidence를 완료한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
### [OFR-REPEAT-1] caller-neutral history preflight
|
||||
|
||||
#### 문제
|
||||
|
||||
- `stream_gate_policy.go:42-50`의 context에는 endpoint/model/scheme/request ref만 있어 incoming role/channel/action history가 없다.
|
||||
- typed Chat decode만 사용하면 `reasoning`, `reasoning_text` 같은 provider alias와 unknown/signed field의 mutation 경계를 안정적으로 구분할 수 없다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
ingress canonical raw body를 endpoint별 parser로 한 번 읽어 immutable/bounded semantic view를 만든다. Chat은 `role=user|assistant`, `content`, `reasoning_content`, `reasoning`, `reasoning_text`, completed tool call/result/error를 분리한다. Responses는 item/reasoning/function-call/result provenance를 자체 parser로 읽고 Chat field를 재사용하지 않는다. whitespace/control normalization 뒤 hash만 filter input에 보존한다.
|
||||
|
||||
Before (`apps/edge/internal/openai/stream_gate_policy.go:42`):
|
||||
|
||||
```go
|
||||
type openAIOutputFilterContext struct {
|
||||
environment string
|
||||
endpoint string
|
||||
modelGroup string
|
||||
hasScheme bool
|
||||
requestRef string
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
type openAIOutputFilterContext struct {
|
||||
environment string
|
||||
endpoint string
|
||||
modelGroup string
|
||||
hasScheme bool
|
||||
requestRef string
|
||||
history openAIRepeatHistorySnapshot
|
||||
}
|
||||
```
|
||||
|
||||
user occurrence가 하나라도 있으면 assistant anchor 후보에서 제외한다. reasoning history 미전송은 occurrence=0으로 처리하고, stable lineage/TTL을 추정하지 않는다. sanitation은 plain assistant reasoning 반복 fragment에만 허용한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/chat_decode.go`: raw Chat history role/channel alias parser.
|
||||
- [ ] `apps/edge/internal/openai/responses_decode.go`: Responses item provenance parser.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_policy.go`: immutable history snapshot 전달.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_policy_test.go`: aliases, user exclusion, history omitted, no identity/TTL, progress/no-progress.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestOpenAIRepeatHistoryChatAliases`, `TestOpenAIRepeatHistoryResponsesProvenance`, `TestOpenAIRepeatHistoryDoesNotInferLineage`, `TestOpenAIRepeatHistoryProgressBoundary`.
|
||||
- signed/encrypted/unknown reasoning, final content, tool args sentinel이 mutation 대상이 아니고 observation raw 값에도 나타나지 않는지 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestOpenAIRepeatHistory'
|
||||
```
|
||||
|
||||
기대 결과: PASS, caller 제품/session에 따른 분기 0건.
|
||||
|
||||
### [OFR-REPEAT-2] Unicode rolling 및 action filter
|
||||
|
||||
#### 문제
|
||||
|
||||
- `stream_gate_filters.go:144-179`는 repeat batch에 항상 `repeat_rolling_clear` pass를 반환한다.
|
||||
- 현재 500-rune hold는 evidence 크기만 정할 뿐 repeated fragment, committed look-behind, action side-effect를 판정하지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
Unicode rune 기준 rolling inspector를 filter request-local state로 구성하고 `EvidenceBatch.ChannelPending()`과 `CommittedLookBehind()`를 함께 읽는다. current response가 아니라 incoming completed tool/result/error만 progress 근거로 사용한다. content 반복은 safe cursor를 가진 continuation intent, unreleased repeated action은 terminal violation, released tool/side-effect 가능 구간은 no-auto-repair fatal decision을 반환한다.
|
||||
|
||||
Before (`apps/edge/internal/openai/stream_gate_filters.go:160`):
|
||||
|
||||
```go
|
||||
descriptor := "repeat_rolling_clear"
|
||||
...
|
||||
return streamgate.NewFilterDecision(
|
||||
streamgate.FilterDecisionKindPass, openAIOutputFilterConsumerID,
|
||||
f.ID(), f.ruleID, evidence, nil,
|
||||
)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
match := f.repeatInspector.Evaluate(f.history, batch)
|
||||
if match.ContentLoop {
|
||||
directive, _ := streamgate.NewRecoveryDirectiveContinuation(match.Cursor, match.SnapshotRef)
|
||||
intent, _ := streamgate.NewRecoveryIntent(
|
||||
streamgate.RecoveryStrategyContinuationRepair, directive,
|
||||
"repeat_content_detected", f.priority,
|
||||
)
|
||||
return repeatRecoveryDecision(match, intent)
|
||||
}
|
||||
return repeatPassOrSafeStop(match)
|
||||
```
|
||||
|
||||
fingerprint/count/offset만 sanitized evidence에 넣고 raw content/reasoning/tool payload는 넣지 않는다. idle은 시간 기반 release가 아니라 evidence 미충족 terminal error로 끝낸다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters.go`: rolling/history/action evaluator, typed intent, sanitized evidence.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters_test.go`: 200/500 rune, Korean UTF-8 split, look-behind, action/progress/side-effect matrix.
|
||||
- [ ] `packages/go/config/edge_types.go`: repeat threshold/bound defaults가 필요할 경우 기존 filter policy 안에서 bounded validation.
|
||||
- [ ] `packages/go/config/stream_evidence_gate_config_test.go`: omitted/default/min/max/invalid config.
|
||||
- [ ] `configs/edge.yaml`: generic repeat policy 예시와 raw-free 설명.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestRepeatGuardKoreanMultibyteRolling`, `TestRepeatGuardCommittedLookBehind`, `TestRepeatGuardActionProgressMatrix`, `TestRepeatGuardIdleDoesNotRelease`.
|
||||
- fixture는 긴 한국어 문단 6개를 UTF-8 multi-byte boundary로 분할하고 다시 반복하며 200/500 rune 두 threshold를 검증한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/go/config ./apps/edge/internal/openai -run 'Test(StreamGate|OpenAI|RepeatGuard).*Repeat'
|
||||
```
|
||||
|
||||
기대 결과: PASS, race/order에 무관한 stable fingerprint/cursor.
|
||||
|
||||
### [OFR-REPEAT-3] continuation dispatch와 downstream 단일성
|
||||
|
||||
#### 문제
|
||||
|
||||
- foundation recovery는 generic patch를 적용하지만 stream-open 뒤 committed safe prefix와 새 attempt opening/prefix 중복을 repeat 의미로 검증하지 않는다.
|
||||
- caller temperature 생략 여부에 따른 `[0.2, 0.4, 0.6]` 순차 후보 계약이 없다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
OFR-RESUME의 recovery source cursor를 사용해 반복 구간을 제외한 content/reasoning만 rebuild한다. caller가 temperature를 명시하지 않은 경우에만 attempt별 0.2→0.4→0.6을 적용하고 명시값은 보존한다. Core commit state가 tool/side-effect 또는 exact-replay 불가 상태면 continuation을 만들지 않는다. ReleaseSink는 첫 response-start/role과 committed prefix를 유지하고 replacement attempt의 opening/prefix를 억제하며 final `[DONE]`/terminal을 한 번만 보낸다.
|
||||
|
||||
Before (`apps/edge/internal/openai/openai_request_rebuilder.go:446`):
|
||||
|
||||
```go
|
||||
case streamgate.RecoveryDirectiveKindContinuation:
|
||||
patchEntry, err = r.patches.takeContinuation(
|
||||
directive.SnapshotRef(), directive.Cursor(),
|
||||
)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
case streamgate.RecoveryDirectiveKindContinuation:
|
||||
source, err = r.recoverySource.Take(
|
||||
directive.SnapshotRef(), directive.Cursor(),
|
||||
)
|
||||
temperature = continuationTemperature(plan.AttemptIndex(), ingressTemperature)
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/openai_request_rebuilder.go`: cursor source와 temperature candidate/body patch.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_runtime.go`: continuation-safe commit/side-effect eligibility와 one-dispatch binding.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_tunnel_codec.go`: replacement opening/role/known prefix와 duplicate terminal 억제.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: temperature/abort/rebuild/dispatch ordering.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: same downstream SSE safe prefix, single `[DONE]`, cancel/tool boundary.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestRepeatGuardContinuationTemperatureCandidates`, `TestRepeatGuardPreservesCallerTemperature`, `TestRepeatGuardStreamOpenNoDuplicatePrefix`, `TestRepeatGuardToolSideEffectNoRepair`.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestRepeatGuard(Continuation|Preserves|StreamOpen|ToolSideEffect)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, recovery cycle당 dispatch 1회, `[DONE]` 1회.
|
||||
|
||||
### [OFR-REPEAT-4] 계약 동기화와 local/dev evidence
|
||||
|
||||
#### 문제
|
||||
|
||||
- `agent-contract/outer/openai-compatible-api.md:94`와 `agent-spec/runtime/stream-evidence-gate.md:99`는 repeat guard를 foundation-only로 기록한다.
|
||||
- roadmap acceptance는 generic fixture 외에 `ornith:35b` capacity+1 동시 요청 5개를 최소 3회 실행한 live evidence를 요구한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
구현과 같은 변경에서 caller-neutral history/current-stream 범위, 500-rune default, safe mutation/side-effect 금지, temperature 후보, degraded no-lineage behavior를 계약/spec에 기록한다. dev smoke는 env-gated test harness가 ignored prompt/artifact directory를 읽고 5 concurrent × 3 runs를 수행하며 raw SSE/output은 `agent-test/runs/**`에만 저장한다. tracked review에는 model/provider/attempt/fingerprint/offset/decision과 `reproduced|not_reproduced`만 기록한다.
|
||||
|
||||
Before (`agent-spec/runtime/stream-evidence-gate.md:99`):
|
||||
|
||||
```text
|
||||
production Core registry는 ... repeat/schema/provider-error foundation ...
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```text
|
||||
repeat_guard는 request-local history/current stream만 사용하고 rolling evidence와 committed look-behind에서 continuation 또는 safe stop을 결정한다.
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: repeat guard caller-visible/stream contract.
|
||||
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`: repeat policy defaults/bounds/restart snapshot.
|
||||
- [ ] `agent-spec/runtime/stream-evidence-gate.md`: current repeat filter lifecycle.
|
||||
- [ ] `agent-spec/input/openai-compatible-surface.md`: raw Chat/Responses history boundary.
|
||||
- [ ] `agent-spec/runtime/provider-pool-config-refresh.md`: provider capability/config snapshot.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: env-gated dev harness 또는 동등한 기존 profile-compatible live entry.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- deterministic tests는 OFR-REPEAT-1~3에서 필수 작성한다.
|
||||
- dev harness는 `IOP_OPENAI_SMOKE_PROMPT_FILE`을 ignored path에서 읽고 credential을 출력하지 않으며, 미재현을 test PASS로 위장하지 않고 sanitized `not_reproduced` evidence로 남긴다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
기대 결과: PASS.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
- runtime predecessor: `01_resume_notice_builder`.
|
||||
- 구현 시작 조건: `agent-task/m-openai-compatible-output-validation-filters/01_resume_notice_builder/complete.log` 존재. 현재 상태는 `missing`.
|
||||
- directory `02+01_repeat_guard`의 `+01` 외 추가 runtime dependency는 없다.
|
||||
|
||||
구현 순서는 OFR-REPEAT-1 → 2 → 3 → 4다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `apps/edge/internal/openai/chat_decode.go` | OFR-REPEAT-1 |
|
||||
| `apps/edge/internal/openai/responses_decode.go` | OFR-REPEAT-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_policy.go` | OFR-REPEAT-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters.go` | OFR-REPEAT-2 |
|
||||
| `packages/go/config/edge_types.go` | OFR-REPEAT-2 |
|
||||
| `configs/edge.yaml` | OFR-REPEAT-2 |
|
||||
| `apps/edge/internal/openai/openai_request_rebuilder.go` | OFR-REPEAT-3 |
|
||||
| `apps/edge/internal/openai/stream_gate_runtime.go` | OFR-REPEAT-3 |
|
||||
| `apps/edge/internal/openai/stream_gate_tunnel_codec.go` | OFR-REPEAT-3 |
|
||||
| `apps/edge/internal/openai/stream_gate_policy_test.go` | OFR-REPEAT-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters_test.go` | OFR-REPEAT-2 |
|
||||
| `packages/go/config/stream_evidence_gate_config_test.go` | OFR-REPEAT-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | OFR-REPEAT-3 |
|
||||
| `apps/edge/internal/openai/stream_gate_vertical_slice_test.go` | OFR-REPEAT-2/3/4 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | OFR-REPEAT-4 |
|
||||
| `agent-contract/inner/edge-config-runtime-refresh.md` | OFR-REPEAT-4 |
|
||||
| `agent-spec/runtime/stream-evidence-gate.md` | OFR-REPEAT-4 |
|
||||
| `agent-spec/input/openai-compatible-surface.md` | OFR-REPEAT-4 |
|
||||
| `agent-spec/runtime/provider-pool-config-refresh.md` | OFR-REPEAT-4 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
Local:
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
Dev preflight와 smoke는 `/Users/toki/agent-work/iop-dev`에서 clean/synced/rebuilt source로 실행한다:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
git rev-parse HEAD
|
||||
git fetch origin
|
||||
git rev-parse origin/main
|
||||
go version && go env GOMOD
|
||||
iop-edge --help
|
||||
iop-edge version
|
||||
go test ./apps/edge/...
|
||||
iop-edge smoke openai --base-url http://127.0.0.1:18083
|
||||
IOP_OPENAI_BASE_URL=http://127.0.0.1:18083 IOP_OPENAI_MODEL=ornith:35b IOP_OPENAI_SMOKE_CONCURRENCY=5 IOP_OPENAI_SMOKE_RUNS=3 IOP_OPENAI_SMOKE_PROMPT_FILE=agent-test/runs/output-filter-recovery/repeat-prompt.txt IOP_OPENAI_SMOKE_ARTIFACT_DIR=agent-test/runs/output-filter-recovery/repeat-guard go test -count=1 -v ./apps/edge/internal/openai -run TestDevRepeatGuardOrnithSmoke
|
||||
```
|
||||
|
||||
기대 결과: local 전부 PASS. dev는 main/HEAD 일치와 clean worktree, Edge config `build/dev-runtime/edge.yaml`/port 18083/`ornith:35b` capacity 4를 확인하고 5 concurrent × 3 runs를 완료한다. raw prompt/SSE/output/token은 stdout이나 tracked 파일에 남기지 않는다. 실제 반복이면 abort/continuation/safe stop evidence, 미재현이면 sanitized `not_reproduced`를 남기며 어느 경우에도 deterministic S03 fixture가 PASS해야 한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/03+02_provider_error_retry plan=0 tag=OFR-PROVIDER -->
|
||||
|
||||
# Code Review Reference - OFR-PROVIDER
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-28
|
||||
task=m-openai-compatible-output-validation-filters/03+02_provider_error_retry, plan=0, tag=OFR-PROVIDER
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `provider-error-retry`: configured code/message matcher와 uncommitted exact replay
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
|
||||
1. 판정과 `review_rework_count` / `evidence_integrity_failure`를 append한다.
|
||||
2. active pair를 `code_review_cloud_G09_0.log`, `plan_cloud_G09_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task directory를 월별 archive로 이동한다.
|
||||
4. PASS이면 milestone 완료 이벤트를 보고하고 roadmap은 직접 수정하지 않는다.
|
||||
5. review-only checklist를 최종 `.log` 위치에서 완료한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| OFR-PROVIDER-1 matcher config와 typed event input | [ ] |
|
||||
| OFR-PROVIDER-2 codec/filter/Core exact replay 연결 | [ ] |
|
||||
| OFR-PROVIDER-3 계약/spec/config 동기화 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-PROVIDER-1] provider-error matcher config와 bounded typed event input을 구현하고 default/validation/raw 비노출 테스트를 추가한다.
|
||||
- [ ] [OFR-PROVIDER-2] tunnel codec과 pure filter를 exact replay intent에 연결하고 uncommitted/abort/re-admission/shared-cap 상태 전이를 검증한다.
|
||||
- [ ] [OFR-PROVIDER-3] outer/inner 계약, 현재 spec, tracked config 예시를 갱신하고 S15-S17 fresh 검증을 통과한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 verdict와 검증된 routing signals를 append한다.
|
||||
- [ ] 판정과 차원별 평가/Required/Suggested/Nit가 일치한다.
|
||||
- [ ] review를 `code_review_cloud_G09_0.log`로 아카이브한다.
|
||||
- [ ] plan을 `plan_cloud_G09_0.log`로 아카이브한다.
|
||||
- [ ] `.gitignore` Agent-Ops 관리 block을 확인한다.
|
||||
- [ ] PASS이면 template 기준 `complete.log`를 작성하고 active `.md`를 남기지 않는다.
|
||||
- [ ] PASS이면 task directory를 월별 archive로 이동한다.
|
||||
- [ ] PASS이면 milestone 완료 이벤트를 보고하고 roadmap/update-roadmap을 직접 호출하지 않는다.
|
||||
- [ ] split parent의 남은 sibling/file을 확인한다.
|
||||
- [ ] WARN/FAIL이면 다음 filesystem state를 작성하고 `complete.log`를 만들지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 deviation과 이유를 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 실제 결정을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- nested matcher element가 code/message 두 필드만 허용하고 omitted default가 승인된 initial element인가.
|
||||
- raw provider message/body가 terminal descriptor, cause, observation, log, task evidence에 유출되지 않는가.
|
||||
- exact+Unicode contains가 같은 element에서만 일치하고 duplicate/order가 semantics를 바꾸지 않는가.
|
||||
- commit/cancel/side-effect/abort failure에서 dispatch가 0회이고 successful cycle당 one abort/rebuild/admission인가.
|
||||
- exact 및 모든 strategy 합계가 최초 실행 제외 0..3을 공유하고 4+가 config에서 거부되는가.
|
||||
- original response start/status/header/body가 숨겨지고 final attempt만 한 번 노출되는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### Dependency
|
||||
|
||||
```bash
|
||||
test -f agent-task/m-openai-compatible-output-validation-filters/02+01_repeat_guard/complete.log
|
||||
```
|
||||
|
||||
_실제 출력/종료 코드를 기록한다._
|
||||
|
||||
### Setup/targeted
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate -run 'TestProviderError'
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestProviderError(Filter|Retry)'
|
||||
```
|
||||
|
||||
_실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
### Full
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
_실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## 섹션 소유권
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute archive/complete procedures |
|
||||
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| Archive Evidence Snapshot | Fixed when present | Only cited archive evidence may be read |
|
||||
| Agent UI Completion | Mixed | Not applicable here |
|
||||
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks only |
|
||||
| 구현 체크리스트 | Fixed at stub creation | Implementing agent checks only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 | Fixed headings/commands | Implementing agent fills actual output |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/03+02_provider_error_retry plan=0 tag=OFR-PROVIDER -->
|
||||
|
||||
# Output Filter Recovery: provider error matcher와 exact replay
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
선행 task의 `complete.log`를 확인한 뒤 구현한다. 완료 전 `CODE_REVIEW-cloud-G09.md`의 구현 에이전트 소유 섹션에 실제 변경과 검증 출력을 채우고 active pair를 유지한 채 review-ready로 보고한다. 종결/archive/`complete.log`는 code-review skill 전용이다. 막히면 정확한 blocker, 시도 명령/출력, 재개 조건만 구현 evidence에 기록하고 사용자 질문, user-input 도구, stop 파일, 다음 상태 분류를 하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
provider tunnel non-2xx는 현재 raw-free generic code로만 `provider_error` event를 만들며 filter는 항상 unmatched pass다. 이 작업은 request-local bounded match input, `{code,message}` config, pure matcher intent를 추가하고 Core의 기존 abort/rebuild/re-admission 및 shared recovery cap을 그대로 사용한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `provider-error-retry`: configured code/message matcher와 uncommitted exact replay
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- 규칙/테스트: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`, `agent-ops/rules/project/domains/edge.md`, `agent-ops/rules/project/domains/platform-common.md`, `agent-ops/rules/project/domains/testing.md`, `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`, `agent-test/dev/rules.md`, `agent-test/dev/edge-smoke.md`, `agent-test/dev/platform-common-smoke.md`, `agent-test/dev/testing-smoke.md`
|
||||
- 로드맵/설계/계약: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, `agent-contract/index.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/stream-evidence-gate.md`, `agent-spec/input/openai-compatible-surface.md`, `agent-spec/runtime/provider-pool-config-refresh.md`
|
||||
- 소스: `packages/go/config/edge_types.go`, `packages/go/streamgate/event.go`, `packages/go/streamgate/filter_contract.go`, `apps/edge/internal/openai/stream_gate_tunnel_codec.go`, `apps/edge/internal/openai/stream_gate_runtime.go`, `apps/edge/internal/openai/stream_gate_filters.go`, `apps/edge/internal/openai/stream_gate_policy.go`, `apps/edge/internal/openai/openai_request_rebuilder.go`, `apps/edge/internal/openai/provider_tunnel.go`, `configs/edge.yaml`, `go.mod`
|
||||
- 테스트: `packages/go/config/stream_evidence_gate_config_test.go`, `packages/go/streamgate/event_test.go`, `apps/edge/internal/openai/stream_gate_filters_test.go`, `apps/edge/internal/openai/stream_gate_policy_test.go`, `apps/edge/internal/openai/stream_gate_pipeline_test.go`, `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`, `apps/edge/internal/openai/filter_observation_sink_test.go`
|
||||
|
||||
### SDD 기준
|
||||
|
||||
- 승인 SDD 대상 S15, S16, S17 → `provider-error-retry`.
|
||||
- Evidence Map의 staged response-start/body matcher, raw-canonical equality, abort/pool re-admission, stream-open/cancel/side-effect no-replay, simultaneous/sequential cross-strategy, request-total 0/1/3 및 4+ rejection을 구현/검증 단위로 사용한다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`. fresh `-count=1` package tests, `git diff --check`, `make test`를 필수로 한다.
|
||||
- provider error는 deterministic tunnel fixture와 fake pool로 모든 S15-S17을 독립 PASS할 수 있어 외부 dev provider를 요구하지 않는다. dev profile은 전체 Milestone 후속 ops smoke 경계를 확인하기 위해 읽었으나 이 패킷의 evidence에는 적용하지 않는다.
|
||||
- setup=`go version && go env GOMOD`; cache 결과는 인정하지 않는다. 누락/blank/`<확인 필요>` 규칙은 없다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- config test는 filter kind/selector/default hold만 검증하고 nested provider matcher shape/default/unknown field를 검증하지 않는다.
|
||||
- event test는 stable external descriptor만 검증하고 bounded code/message match input의 copy/validation/non-exposure를 검증하지 않는다.
|
||||
- tunnel/vertical slice는 generic provider error lifecycle만 검증하고 known/second matcher, staged body, commit matrix, cross-strategy cap을 검증하지 않는다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- rename/remove 없음.
|
||||
- `NewProviderErrorEvent` 호출점은 `stream_gate_runtime.go:80` 및 package tests다. 호환 constructor는 유지하고 typed match-input constructor/accessor를 추가해 기존 호출을 깨지 않는다.
|
||||
- `newOpenAIOutputFilter` 호출점은 `stream_gate_policy.go:94`; provider matcher snapshot을 constructor에 추가할 때 다른 filter kind call도 compile-time 갱신한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- 안정 계약: “bounded structured provider error가 같은 config element의 exact code + Unicode contains message에 맞고 transport uncommitted일 때만 exact intent 하나를 만들며, Core의 기존 total/strategy cap과 pool admission을 우회하지 않는다.”
|
||||
- predecessor `02_repeat_guard`는 directory `02+01_repeat_guard`의 PASS `complete.log`로 충족해야 한다. 현재 active/archive 후보에 `complete.log`가 없어 `missing`이다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- provider 선택/submit/counter/snapshot은 filter에 넣지 않고 기존 Core/dispatcher 소유로 유지한다.
|
||||
- raw response body 전체, position suffix, generated output은 config/observation/log에 저장하지 않는다.
|
||||
- status/header/body public passthrough 계약은 최종 성공 attempt 한 번 외에는 변경하지 않는다.
|
||||
- schema/continuation semantics와 managed length는 제외한다. 외부 dependency도 추가하지 않는다.
|
||||
|
||||
### 최종 라우팅
|
||||
|
||||
- evaluation_mode=`first-pass`; finalizer=`finalize-task-policy.sh pair`.
|
||||
- build closure: scope=2, state=2, blast=2, evidence=1, verification=2 → G09; base/final=`grade-boundary`, lane=`cloud`, filename=`PLAN-cloud-G09.md`.
|
||||
- review closure: 같은 G09, route=`official-review`, lane=`cloud`, filename=`CODE_REVIEW-cloud-G09.md`.
|
||||
- `large_indivisible_context=false`.
|
||||
- loop risks: `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product` (4).
|
||||
- recovery: `review_rework_count=0`, `evidence_integrity_failure=false`.
|
||||
- capability gap: 없음.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-PROVIDER-1] provider-error matcher config와 bounded typed event input을 구현하고 default/validation/raw 비노출 테스트를 추가한다.
|
||||
- [ ] [OFR-PROVIDER-2] tunnel codec과 pure filter를 exact replay intent에 연결하고 uncommitted/abort/re-admission/shared-cap 상태 전이를 검증한다.
|
||||
- [ ] [OFR-PROVIDER-3] outer/inner 계약, 현재 spec, tracked config 예시를 갱신하고 S15-S17 fresh 검증을 통과한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
### [OFR-PROVIDER-1] matcher config와 typed event input
|
||||
|
||||
#### 문제
|
||||
|
||||
- `packages/go/config/edge_types.go:236-254`에는 provider error match elements가 없다.
|
||||
- `packages/go/streamgate/event.go:306-310`의 provider error payload는 public terminal descriptor만 가지며 실제 code/message를 matcher가 읽을 수 없다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
provider-error policy에만 다음 nested `filters[]`를 허용한다. 각 element는 YAML/mapstructure strict decode 기준 `code`, `message` 두 필드만 가진다.
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
- filter: provider_error
|
||||
filters:
|
||||
- code: 500
|
||||
message: Failed to parse input at pos
|
||||
```
|
||||
|
||||
omitted/empty nested list는 위 초기 element 하나로 정규화한다. code는 valid HTTP/error numeric range, message는 non-empty bounded Unicode string이다. 다른 filter kind의 nested list, unknown field, invalid code/empty message는 거부한다. 순서/중복은 의미에 영향을 주지 않는다.
|
||||
|
||||
Before (`packages/go/streamgate/event.go:306`):
|
||||
|
||||
```go
|
||||
// terminal / provider_error
|
||||
terminalSuccess bool
|
||||
terminalMeta *TerminalMetadata
|
||||
externalDesc *ExternalDescriptor
|
||||
failureCauses FailureCauseChain
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
// provider_error: request-local matcher input; excluded from terminal/log views.
|
||||
providerErrorMatch *ProviderErrorMatchInput
|
||||
```
|
||||
|
||||
`ProviderErrorMatchInput{Code int, Message string}`는 length bound와 defensive copy를 강제하고 `AsProviderErrorMatchInput` 외 terminal result/descriptor/cause/observation serialization에 포함하지 않는다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/go/config/edge_types.go`: matcher types/default/effective/validation.
|
||||
- [ ] `packages/go/config/stream_evidence_gate_config_test.go`: exact shape, default, second element, duplicate/order, unknown/invalid cases.
|
||||
- [ ] `packages/go/streamgate/event.go`: typed bounded match input constructor/accessor/copy/Validate.
|
||||
- [ ] `packages/go/streamgate/event_test.go`: copy isolation, max length, wrong-kind access, descriptor non-exposure.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestProviderErrorFilterDefaults`, `TestProviderErrorFilterValidation`, `TestProviderErrorMatchInputIsBoundedAndPrivate`.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate -run 'TestProviderError'
|
||||
```
|
||||
|
||||
기대 결과: PASS, default matcher exact, raw message가 terminal descriptor/cause 출력에 없음.
|
||||
|
||||
### [OFR-PROVIDER-2] codec/filter/Core exact replay 연결
|
||||
|
||||
#### 문제
|
||||
|
||||
- `stream_gate_runtime.go:64-80`는 stable generic code만 event에 넣는다.
|
||||
- `stream_gate_tunnel_codec.go:307-317`는 staged wire body를 generic `tunnel_failed`로 바꾼다.
|
||||
- `stream_gate_filters.go:164-169`는 error 존재 여부만 보고 unmatched pass한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
tunnel codec은 non-2xx structured body에서 `{error:{code,message}}` 또는 HTTP status + bounded message를 endpoint별로 파싱한다. 전체 body는 기존 wire staging에만 있고 match input에는 bounded code/message만 복사한다. matcher는 같은 element에서 `code == observed.Code`와 `strings.Contains(observed.Message, rule.Message)`를 모두 만족할 때 first stable matcher index를 sanitized evidence로 남기고 exact directive/intent를 반환한다.
|
||||
|
||||
Before (`apps/edge/internal/openai/stream_gate_tunnel_codec.go:315`):
|
||||
|
||||
```go
|
||||
if providerError {
|
||||
ev, err := newOpenAIProviderErrorEvent(streamGateErrorTunnelFailed)
|
||||
return []streamgate.NormalizedEvent{ev}, err
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
if providerError {
|
||||
matchInput := decodeProviderErrorMatchInput(payload, c.status)
|
||||
ev, err := newOpenAIProviderErrorEventWithMatch(
|
||||
streamGateErrorTunnelFailed, matchInput,
|
||||
)
|
||||
return []streamgate.NormalizedEvent{ev}, err
|
||||
}
|
||||
```
|
||||
|
||||
Core가 `transport_uncommitted`일 때만 intent를 선택한다. current attempt abort 성공 뒤 exact raw-canonical Rebuilder와 기존 pool admission을 cycle당 한 번 호출한다. `max_request_fault_recovery`를 SDD의 request-total `max_recovery_attempts_total` representation으로 유지하며 0..3/strategy cap을 Tool validation·continuation·schema와 공유한다. status/header/role/body commit, cancel, side-effect, abort failure에서는 새 dispatch가 0회다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_tunnel_codec.go`: structured/bounded error parse, staged body lifecycle.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_runtime.go`: typed event construction, commit/cancel/abort guard 유지.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_policy.go`: immutable matcher snapshot을 provider filter에 전달.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters.go`: pure same-element matcher와 exact intent/sanitized evidence.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters_test.go`: known/second/mismatch/order/duplicate/raw-free decision.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: abort-before-rebuild/dispatch 및 one-plan.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: staged body, commit matrix, shared cap, final start/terminal 단일성.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestProviderErrorFilterMatchesConfiguredPair`, `TestProviderErrorRetryStagedResponse`, `TestProviderErrorRetryCommitMatrix`, `TestProviderErrorRetrySharedRecoveryCaps`, `TestProviderErrorRetryAbortFailure`.
|
||||
- 0/1/3 허용, 4+ config rejection, simultaneous/serial tool-validation intent, filter mismatch, stream-open, cancel을 table fixture로 고정한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestProviderError(Filter|Retry)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, original status/header/body 노출 0회, final response-start 1회, cycle dispatch 최대 1회.
|
||||
|
||||
### [OFR-PROVIDER-3] 계약/spec/config 동기화
|
||||
|
||||
#### 문제
|
||||
|
||||
- `agent-contract/inner/edge-config-runtime-refresh.md:36`은 provider matcher가 없는 foundation으로 기록한다.
|
||||
- `configs/edge.yaml:143-145`도 provider_error를 lifecycle observation-only로 설명한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
nested matcher shape/default, exact+contains semantics, raw suffix 금지, uncommitted-only, total/strategy shared cap, existing pool re-admission을 outer/inner contract와 current specs/example에 반영한다.
|
||||
|
||||
Before (`configs/edge.yaml:143`):
|
||||
|
||||
```yaml
|
||||
# - filter: provider_error
|
||||
# enabled: true
|
||||
# capability: output.provider_error
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```yaml
|
||||
# - filter: provider_error
|
||||
# filters:
|
||||
# - code: 500
|
||||
# message: Failed to parse input at pos
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `configs/edge.yaml`: safe matcher example.
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: caller-visible retry/commit contract.
|
||||
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`: config/default/validation/restart snapshot.
|
||||
- [ ] `agent-spec/runtime/stream-evidence-gate.md`: current matcher/recovery lifecycle.
|
||||
- [ ] `agent-spec/input/openai-compatible-surface.md`: final-attempt-only response surface.
|
||||
- [ ] `agent-spec/runtime/provider-pool-config-refresh.md`: pool re-admission/config behavior.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 문서 전용 test는 추가하지 않는다. config, event, vertical slice executable tests와 `git diff --check`를 evidence로 사용한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
기대 결과: PASS.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
- predecessor: `02+01_repeat_guard`.
|
||||
- 시작 조건: `agent-task/m-openai-compatible-output-validation-filters/02+01_repeat_guard/complete.log`.
|
||||
- 현재 predecessor 상태: `missing`. directory `03+02_provider_error_retry`의 `+02` 외 추가 runtime dependency는 없다.
|
||||
- OFR-PROVIDER-1 → 2 → 3 순서로 구현한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `packages/go/config/edge_types.go` | OFR-PROVIDER-1 |
|
||||
| `packages/go/config/stream_evidence_gate_config_test.go` | OFR-PROVIDER-1 |
|
||||
| `packages/go/streamgate/event.go` | OFR-PROVIDER-1 |
|
||||
| `packages/go/streamgate/event_test.go` | OFR-PROVIDER-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_tunnel_codec.go` | OFR-PROVIDER-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_runtime.go` | OFR-PROVIDER-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_policy.go` | OFR-PROVIDER-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters.go` | OFR-PROVIDER-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters_test.go` | OFR-PROVIDER-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | OFR-PROVIDER-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_vertical_slice_test.go` | OFR-PROVIDER-2 |
|
||||
| `configs/edge.yaml` | OFR-PROVIDER-3 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | OFR-PROVIDER-3 |
|
||||
| `agent-contract/inner/edge-config-runtime-refresh.md` | OFR-PROVIDER-3 |
|
||||
| `agent-spec/runtime/stream-evidence-gate.md` | OFR-PROVIDER-3 |
|
||||
| `agent-spec/input/openai-compatible-surface.md` | OFR-PROVIDER-3 |
|
||||
| `agent-spec/runtime/provider-pool-config-refresh.md` | OFR-PROVIDER-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate -run 'TestProviderError'
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestProviderError(Filter|Retry)'
|
||||
go test -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
기대 결과: 모두 PASS, cached 결과 미사용. S15-S17에서 known/second matcher, staged status/body, raw-canonical rebuild, uncommitted-only, 0/1/3 total cap, 4+ rejection, cross-strategy one-plan, abort-failure no-dispatch가 확인된다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/04+03_schema_contract plan=0 tag=OFR-SCHEMA -->
|
||||
|
||||
# Code Review Reference - OFR-SCHEMA
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-28
|
||||
task=m-openai-compatible-output-validation-filters/04+03_schema_contract, plan=0, tag=OFR-SCHEMA
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `schema-contract`: bounded terminal JSON schema validation과 lossless repair
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과`의 출력이 코드와 일치하는지 확인한다.
|
||||
|
||||
1. 판정과 `review_rework_count` / `evidence_integrity_failure`를 append한다.
|
||||
2. active pair를 `code_review_cloud_G09_0.log`, `plan_cloud_G09_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task directory를 월별 archive로 이동한다.
|
||||
4. PASS이면 milestone 완료 이벤트를 보고하고 roadmap은 직접 수정하지 않는다.
|
||||
5. review-only checklist를 최종 `.log` 위치에서 완료한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|---|---|
|
||||
| OFR-SCHEMA-1 ingress schema contract와 endpoint instruction | [ ] |
|
||||
| OFR-SCHEMA-2 bounded terminal validation과 repair | [ ] |
|
||||
| OFR-SCHEMA-3 계약/spec/example와 전체 회귀 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-SCHEMA-1] `metadata.scheme` object와 required `max_buffer_runes`를 request-start contract로 파싱하고 endpoint별 마지막 user input에 schema instruction을 lossless append한다.
|
||||
- [ ] [OFR-SCHEMA-2] bounded terminal schema filter와 typed repair/Rebuilder를 구현해 valid-only commit, cap/overflow fail-closed를 보장한다.
|
||||
- [ ] [OFR-SCHEMA-3] metadata/config/endpoint/vertical fixtures와 outer/inner 계약·현재 spec을 갱신하고 S05/S06/S19 fresh 검증을 통과한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 verdict와 검증된 routing signals를 append한다.
|
||||
- [ ] 판정과 차원별 평가/Required/Suggested/Nit가 일치한다.
|
||||
- [ ] review를 `code_review_cloud_G09_0.log`로 아카이브한다.
|
||||
- [ ] plan을 `plan_cloud_G09_0.log`로 아카이브한다.
|
||||
- [ ] `.gitignore` Agent-Ops 관리 block을 확인한다.
|
||||
- [ ] PASS이면 template 기준 `complete.log`를 작성하고 active `.md`를 남기지 않는다.
|
||||
- [ ] PASS이면 task directory를 월별 archive로 이동한다.
|
||||
- [ ] PASS이면 milestone 완료 이벤트를 보고하고 roadmap/update-roadmap을 직접 호출하지 않는다.
|
||||
- [ ] split parent의 남은 sibling/file을 확인한다.
|
||||
- [ ] WARN/FAIL이면 다음 filesystem state를 작성하고 `complete.log`를 만들지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 deviation과 이유를 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 실제 결정을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `metadata.scheme`만 object로 허용/제외하고 기존 metadata string/workspace validation은 유지되는가.
|
||||
- schema instruction이 endpoint별 마지막 user input에만 append되고 unknown/multimodal fields가 보존되는가.
|
||||
- `max_buffer_runes`가 explicit request-start policy이며 terminal gate가 초과 전 partial을 release하지 않는가.
|
||||
- valid single JSON만 commit되고 parse/schema failure는 raw-free typed repair로 이어지는가.
|
||||
- request-total/strategy/snapshot cap 소진 시 draft/dispatch 0회, final terminal 1회인가.
|
||||
- existing schema subset을 재사용하고 지원 범위를 contract에 정확히 제한했는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### Dependency
|
||||
|
||||
```bash
|
||||
test -f agent-task/m-openai-compatible-output-validation-filters/03+02_provider_error_retry/complete.log
|
||||
```
|
||||
|
||||
_실제 출력/종료 코드를 기록한다._
|
||||
|
||||
### Setup/targeted
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'Test(ParseOpenAIMetadataExcludesScheme|Schema(Gate|Repair|Contract)|ChatSchemaInstruction|ResponsesSchemaInstruction)'
|
||||
```
|
||||
|
||||
_실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
### Full
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
_실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## 섹션 소유권
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute archive/complete procedures |
|
||||
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| Archive Evidence Snapshot | Fixed when present | Only cited archive evidence may be read |
|
||||
| Agent UI Completion | Mixed | Not applicable here |
|
||||
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks only |
|
||||
| 구현 체크리스트 | Fixed at stub creation | Implementing agent checks only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 | Fixed headings/commands | Implementing agent fills actual output |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/04+03_schema_contract plan=0 tag=OFR-SCHEMA -->
|
||||
|
||||
# Output Filter Recovery: metadata.scheme terminal contract
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
선행 task의 `complete.log`를 확인한 뒤 구현한다. 완료 전 `CODE_REVIEW-cloud-G09.md`의 구현 소유 섹션을 실제 변경/검증 출력으로 채우고 active pair를 유지한 채 review-ready로 보고한다. verdict, archive, `complete.log`는 code-review skill 전용이다. blocker는 시도한 명령/출력과 재개 조건만 기록하고 사용자 질문, user-input, stop 파일, 다음 상태 분류를 하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
`metadata.scheme`은 현재 schema filter 참여 여부만 결정하며 metadata flattening은 object 값을 거부한다. `schema_gate`도 terminal hold 뒤 항상 pass한다. 이 작업은 schema object를 lossless request contract로 해석하고, bounded terminal validation과 typed schema repair를 통해 valid JSON만 commit하도록 만든다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `schema-contract`: bounded terminal JSON schema validation과 lossless repair
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- 규칙/테스트: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`, `agent-ops/rules/project/domains/edge.md`, `agent-ops/rules/project/domains/platform-common.md`, `agent-ops/rules/project/domains/testing.md`, `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`
|
||||
- 로드맵/설계/계약: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, `agent-contract/index.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/stream-evidence-gate.md`, `agent-spec/input/openai-compatible-surface.md`, `agent-spec/runtime/provider-pool-config-refresh.md`
|
||||
- 소스: `apps/edge/internal/openai/responses_decode.go`, `apps/edge/internal/openai/chat_decode.go`, `apps/edge/internal/openai/chat_handler.go`, `apps/edge/internal/openai/responses_handler.go`, `apps/edge/internal/openai/stream_gate_policy.go`, `apps/edge/internal/openai/stream_gate_filters.go`, `apps/edge/internal/openai/openai_request_rebuilder.go`, `apps/edge/internal/openai/stream_gate_runtime.go`, `apps/edge/internal/openai/tool_validation.go`의 기존 JSON schema subset validator, `packages/go/config/edge_types.go`, `packages/go/streamgate/filter_contract.go`, `packages/go/streamgate/evidence_tail.go`의 bounded terminal-gate API, `configs/edge.yaml`, `go.mod`
|
||||
- 테스트: `apps/edge/internal/openai/workspace_metadata_test.go`, `apps/edge/internal/openai/responses_handler_test.go`, `apps/edge/internal/openai/stream_gate_filters_test.go`, `apps/edge/internal/openai/stream_gate_policy_test.go`, `apps/edge/internal/openai/stream_gate_pipeline_test.go`, `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`, `apps/edge/internal/openai/chat_tool_synthesis_test.go`, `packages/go/config/stream_evidence_gate_config_test.go`
|
||||
|
||||
### SDD 기준
|
||||
|
||||
- 승인 SDD 대상 S05, S06, S19 → `schema-contract`.
|
||||
- Evidence Map은 valid JSON validated-only commit, invalid common recovery/unknown-field preservation/retry exhaustion, hard-limit overflow/cancel/no partial release를 요구한다. `stream=true`에서도 response-start/content eager commit이 0회여야 한다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`. edge/platform-common/testing profile의 fresh `-count=1`, `git diff --check`, `make test`를 사용한다.
|
||||
- schema behavior는 deterministic codec/Rebuilder fixture로 독립 PASS하므로 외부 dev smoke는 요구하지 않는다.
|
||||
- setup=`go version && go env GOMOD`; cached 결과는 evidence로 인정하지 않는다. profile 누락/blank/`<확인 필요>`는 없다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- metadata tests는 모든 non-string value를 거부하므로 `scheme` object를 제외하고 다른 metadata validation을 유지하는 fixture가 없다.
|
||||
- schema filter tests는 presence/hold/pass만 검증하고 JSON parse/subset validation/typed intent를 검증하지 않는다.
|
||||
- Rebuilder tests는 generic schema patch는 있으나 scheme instruction, validation summary, multimodal/unknown field 보존을 검증하지 않는다.
|
||||
- vertical slice는 max buffer overflow와 stream eager commit 금지를 검증하지 않는다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- rename/remove 없음.
|
||||
- `parseOpenAIMetadata` 호출점은 Chat/Responses request preparation 경로 전체다. return signature를 바꾸지 않고 별도 `parseOpenAISchemaContract`를 추가해 workspace/기존 metadata call site를 보존한다.
|
||||
- `validateJSONSchemaSubset`는 `tool_validation.go:142`와 tool synthesis tests에서 사용된다. 함수를 이동/rename하지 않고 schema filter가 같은 package helper를 재사용한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- 안정 계약: “explicit schema object가 있으면 request-start 고정 hard bound로 terminal gate를 적용하고, valid JSON만 release하며 invalid는 uncommitted typed repair, overflow/cap 소진은 partial-free terminal로 끝낸다.”
|
||||
- predecessor는 `03+02_provider_error_retry`; 현재 `complete.log`가 없어 `missing`. `04+03_schema_contract`의 `+03` 외 runtime dependency는 없다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- JSON Schema 전체 draft 지원이나 새 dependency는 추가하지 않고 기존 subset(type/object/array/required/enum/anyOf/oneOf/allOf/additionalProperties)을 계약에 명시한다.
|
||||
- `metadata.scheme`을 공개 wrapper나 execution path selector로 사용하지 않는다.
|
||||
- raw tunnel을 normalized route로 바꾸지 않고 endpoint별 raw body parser/Rebuilder를 유지한다.
|
||||
- repeat/provider/managed length 의미는 변경하지 않는다.
|
||||
|
||||
### 최종 라우팅
|
||||
|
||||
- evaluation_mode=`first-pass`; finalizer=`finalize-task-policy.sh pair`.
|
||||
- build closure: scope=2, state=2, blast=2, evidence=1, verification=2 → G09; base/final=`grade-boundary`, lane=`cloud`, filename=`PLAN-cloud-G09.md`.
|
||||
- review closure: 같은 G09, route=`official-review`, lane=`cloud`, filename=`CODE_REVIEW-cloud-G09.md`.
|
||||
- `large_indivisible_context=false`.
|
||||
- loop risks: `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product` (4).
|
||||
- recovery: `review_rework_count=0`, `evidence_integrity_failure=false`.
|
||||
- capability gap: 없음.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-SCHEMA-1] `metadata.scheme` object와 required `max_buffer_runes`를 request-start contract로 파싱하고 endpoint별 마지막 user input에 schema instruction을 lossless append한다.
|
||||
- [ ] [OFR-SCHEMA-2] bounded terminal schema filter와 typed repair/Rebuilder를 구현해 valid-only commit, cap/overflow fail-closed를 보장한다.
|
||||
- [ ] [OFR-SCHEMA-3] metadata/config/endpoint/vertical fixtures와 outer/inner 계약·현재 spec을 갱신하고 S05/S06/S19 fresh 검증을 통과한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
### [OFR-SCHEMA-1] ingress schema contract와 endpoint instruction
|
||||
|
||||
#### 문제
|
||||
|
||||
- `responses_decode.go:76-108`은 `metadata`의 모든 값에 `metadataStringValue`를 적용해 optional JSON schema object를 거부한다.
|
||||
- `stream_gate_policy.go:15-32`는 scheme presence만 보며 shape/ref를 보존하지 않는다.
|
||||
- initial provider request에 schema instruction을 append하는 경로가 없다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`metadata.scheme`만 JSON object로 별도 parse/validate/canonicalize하고 flat run metadata에서는 제외한다. 나머지 metadata는 기존 string/size/key validation을 유지한다. canonical schema의 stable request-local ref를 만들고, raw-canonical body map에서 Chat의 마지막 `role=user` content 또는 Responses의 마지막 user input에 deterministic output-schema instruction을 append한다. multimodal/unknown fields는 map의 해당 text slot 외 byte-equivalent semantic value를 유지한다.
|
||||
|
||||
Before (`apps/edge/internal/openai/responses_decode.go:90`):
|
||||
|
||||
```go
|
||||
for key, rawValue := range rawMap {
|
||||
...
|
||||
value, err := metadataStringValue(key, rawValue)
|
||||
...
|
||||
flat[key] = value
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
for key, rawValue := range rawMap {
|
||||
if key == "scheme" {
|
||||
continue // parsed by parseOpenAISchemaContract
|
||||
}
|
||||
value, err := metadataStringValue(key, rawValue)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
schema가 없으면 기존 request bytes/behavior를 유지한다. schema가 있는데 마지막 user input에 lossless append할 수 없으면 dispatch 전 `invalid_request_error`로 종료한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/responses_decode.go`: scheme object 분리, flat metadata 기존 validation 유지.
|
||||
- [ ] `apps/edge/internal/openai/chat_decode.go`: Chat metadata scheme parse 연계.
|
||||
- [ ] `apps/edge/internal/openai/chat_handler.go`: dispatch 전 last-user schema instruction body 준비.
|
||||
- [ ] `apps/edge/internal/openai/responses_handler.go`: Responses endpoint-native input instruction 준비.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_policy.go`: immutable schema ref/body/bound context.
|
||||
- [ ] `apps/edge/internal/openai/workspace_metadata_test.go`: scheme object 허용 + workspace/string metadata 회귀.
|
||||
- [ ] `apps/edge/internal/openai/responses_handler_test.go`: Responses schema input shape/unknown field.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestParseOpenAIMetadataExcludesSchemeObject`, `TestSchemaContractPreservesWorkspaceMetadata`, `TestChatSchemaInstructionPreservesUnknownFields`, `TestResponsesSchemaInstructionPreservesMultimodalInput`.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'Test(ParseOpenAIMetadataExcludesScheme|SchemaContractPreserves|ChatSchemaInstruction|ResponsesSchemaInstruction)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, scheme은 dispatch metadata에 없음, 다른 metadata 계약 유지.
|
||||
|
||||
### [OFR-SCHEMA-2] bounded terminal validation과 repair
|
||||
|
||||
#### 문제
|
||||
|
||||
- `stream_gate_filters.go:122-131`은 default max buffer terminal gate를 만들고 request policy의 explicit hard bound를 사용하지 않는다.
|
||||
- `stream_gate_filters.go:160-179`은 terminal output을 parse/validate하지 않고 pass한다.
|
||||
- generic schema patch store는 typed schema/summary source와 cap exhaustion 경계를 강제하지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
schema_gate policy에 explicit `max_buffer_runes`를 필수로 추가하고 valid range를 existing Core bound와 맞춘다. filter는 `NewFilterHoldRequirementTerminalGateWithMaxBuffer`를 사용한다. terminal에서 content channel 전체를 single JSON value로 parse하고 기존 `validateJSONSchemaSubset`로 canonical schema를 검증한다. valid면 pass/release, invalid면 schemaRef와 sanitized patch code만 가진 `schema_repair` intent를 반환한다.
|
||||
|
||||
Before (`apps/edge/internal/openai/stream_gate_filters.go:123`):
|
||||
|
||||
```go
|
||||
req, err := streamgate.NewFilterHoldRequirementTerminalGate(
|
||||
f.channel,
|
||||
[]streamgate.EventKind{streamgate.EventKindTextDelta, streamgate.EventKindTerminal},
|
||||
streamgate.EventKindTerminal,
|
||||
)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
req, err := streamgate.NewFilterHoldRequirementTerminalGateWithMaxBuffer(
|
||||
f.channel,
|
||||
[]streamgate.EventKind{streamgate.EventKindTextDelta, streamgate.EventKindTerminal},
|
||||
streamgate.EventKindTerminal,
|
||||
f.maxBufferRunes,
|
||||
)
|
||||
```
|
||||
|
||||
Rebuilder는 request-local schema store에서 canonical schema와 bounded validation summary를 읽어 기존 schema instruction에 repair instruction을 append한다. original canonical body와 multimodal/unknown field는 보존한다. request-total/strategy cap 또는 ingress snapshot limit 소진 시 새 draft/dispatch를 만들지 않는다. overflow는 Core max-buffer signal에서 current attempt를 cancel하고 staged start/content를 버린 뒤 terminal error 하나만 보낸다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/go/config/edge_types.go`: schema-only required `max_buffer_runes`/range/other-kind rejection.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters.go`: bounded terminal requirement, JSON parse/schema validate, typed intent/evidence.
|
||||
- [ ] `apps/edge/internal/openai/openai_request_rebuilder.go`: request-local schema store와 lossless repair instruction.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_runtime.go`: schema store wiring, overflow/cap no-dispatch.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters_test.go`: valid/invalid/parse/trailing/typed intent/hard bound.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_pipeline_test.go`: invalid→repair→valid, cap/snapshot exhaustion.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: stream=true eager start/content 0, overflow/cancel/single terminal.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestSchemaGateValidJSON`, `TestSchemaGateInvalidReturnsTypedRepair`, `TestSchemaGateHardLimitNoPartialRelease`, `TestSchemaRepairPreservesUnknownFields`, `TestSchemaRepairCapsDoNotDispatch`.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/go/config ./apps/edge/internal/openai -run 'TestSchema(Gate|Repair|Contract)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, invalid/overflow output partial 0 rune.
|
||||
|
||||
### [OFR-SCHEMA-3] 계약/spec/example와 전체 회귀
|
||||
|
||||
#### 문제
|
||||
|
||||
- `agent-contract/outer/openai-compatible-api.md:92-94`는 schema_gate participation/foundation만 설명한다.
|
||||
- `configs/edge.yaml:140-142`는 required hard bound나 scheme subset을 설명하지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`metadata.scheme` object, supported subset, last-user instruction, required max bound, `stream=true` terminal gating, valid-only release, typed repair, unknown-field preservation, cap/overflow terminal을 outer/inner contracts와 current specs/config example에 동기화한다.
|
||||
|
||||
Before (`configs/edge.yaml:140`):
|
||||
|
||||
```yaml
|
||||
# - filter: schema_gate
|
||||
# enabled: true
|
||||
# capability: output.schema_gate
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```yaml
|
||||
# - filter: schema_gate
|
||||
# max_buffer_runes: 65536
|
||||
# capability: output.schema_gate
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/go/config/stream_evidence_gate_config_test.go`: schema max bound required/default rejection/min/max.
|
||||
- [ ] `configs/edge.yaml`: schema policy example/subset note.
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: public metadata/output/stream/error contract.
|
||||
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`: config validation/request snapshot.
|
||||
- [ ] `agent-spec/runtime/stream-evidence-gate.md`: current schema lifecycle.
|
||||
- [ ] `agent-spec/input/openai-compatible-surface.md`: endpoint request/response shape.
|
||||
- [ ] `agent-spec/runtime/provider-pool-config-refresh.md`: restart/config snapshot behavior.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 문서 전용 test는 추가하지 않는다. OFR-SCHEMA-1/2의 executable tests와 config tests를 evidence로 사용한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
기대 결과: PASS.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
- predecessor: `03+02_provider_error_retry`.
|
||||
- 시작 조건: `agent-task/m-openai-compatible-output-validation-filters/03+02_provider_error_retry/complete.log`.
|
||||
- 현재 상태: `missing`. directory `04+03_schema_contract`의 `+03` 외 추가 dependency는 없다.
|
||||
- OFR-SCHEMA-1 → 2 → 3 순서로 구현한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `apps/edge/internal/openai/responses_decode.go` | OFR-SCHEMA-1 |
|
||||
| `apps/edge/internal/openai/chat_decode.go` | OFR-SCHEMA-1 |
|
||||
| `apps/edge/internal/openai/chat_handler.go` | OFR-SCHEMA-1 |
|
||||
| `apps/edge/internal/openai/responses_handler.go` | OFR-SCHEMA-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_policy.go` | OFR-SCHEMA-1 |
|
||||
| `apps/edge/internal/openai/workspace_metadata_test.go` | OFR-SCHEMA-1 |
|
||||
| `apps/edge/internal/openai/responses_handler_test.go` | OFR-SCHEMA-1 |
|
||||
| `packages/go/config/edge_types.go` | OFR-SCHEMA-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters.go` | OFR-SCHEMA-2 |
|
||||
| `apps/edge/internal/openai/openai_request_rebuilder.go` | OFR-SCHEMA-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_runtime.go` | OFR-SCHEMA-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters_test.go` | OFR-SCHEMA-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_pipeline_test.go` | OFR-SCHEMA-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_vertical_slice_test.go` | OFR-SCHEMA-2 |
|
||||
| `packages/go/config/stream_evidence_gate_config_test.go` | OFR-SCHEMA-3 |
|
||||
| `configs/edge.yaml` | OFR-SCHEMA-3 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | OFR-SCHEMA-3 |
|
||||
| `agent-contract/inner/edge-config-runtime-refresh.md` | OFR-SCHEMA-3 |
|
||||
| `agent-spec/runtime/stream-evidence-gate.md` | OFR-SCHEMA-3 |
|
||||
| `agent-spec/input/openai-compatible-surface.md` | OFR-SCHEMA-3 |
|
||||
| `agent-spec/runtime/provider-pool-config-refresh.md` | OFR-SCHEMA-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'Test(ParseOpenAIMetadataExcludesScheme|Schema(Gate|Repair|Contract)|ChatSchemaInstruction|ResponsesSchemaInstruction)'
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
기대 결과: 모두 PASS, cached evidence 미사용. S05/S06/S19의 valid-only commit, invalid→repair, exhausted/total cap, hard/snapshot overflow, unknown/multimodal preservation, eager output 0건이 확인된다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/05+04_ops_evidence plan=0 tag=OFR-OPS -->
|
||||
|
||||
# Code Review Reference - OFR-OPS
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-28
|
||||
task=m-openai-compatible-output-validation-filters/05+04_ops_evidence, plan=0, tag=OFR-OPS
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `ops-evidence`: correlated raw-free filter evidence와 configurable assembled output logging
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과`의 local/dev sanitized evidence가 코드와 일치하는지 확인한다.
|
||||
|
||||
1. 판정과 `review_rework_count` / `evidence_integrity_failure`를 append한다.
|
||||
2. active pair를 `code_review_cloud_G09_0.log`, `plan_cloud_G09_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 task directory를 월별 archive로 이동한다.
|
||||
4. PASS이면 milestone 완료 이벤트를 보고하고 roadmap은 직접 수정하지 않는다.
|
||||
5. review-only checklist를 최종 `.log` 위치에서 완료한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|---|---|
|
||||
| OFR-OPS-1 request-start raw output logging policy | [ ] |
|
||||
| OFR-OPS-2 filter observation correlation과 generic fixture | [ ] |
|
||||
| OFR-OPS-3 계약 동기화와 dev evidence | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-OPS-1] default-on/request-start assembled output logging policy를 Chat/Responses/legacy/gated/tunnel 전 경로에 적용하고 on/off/always-redacted 테스트를 추가한다.
|
||||
- [ ] [OFR-OPS-2] semantic filter observation의 stable correlation/filter/rule/fingerprint/count/offset/release-or-close axes를 generic raw HTTP/OpenAI SDK fixture로 검증한다.
|
||||
- [ ] [OFR-OPS-3] outer/inner 계약·현재 spec/config 예시를 갱신하고 dev `ornith:35b` capacity+1 × 3 sanitized evidence를 완료한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 verdict와 검증된 routing signals를 append한다.
|
||||
- [ ] 판정과 차원별 평가/Required/Suggested/Nit가 일치한다.
|
||||
- [ ] review를 `code_review_cloud_G09_0.log`로 아카이브한다.
|
||||
- [ ] plan을 `plan_cloud_G09_0.log`로 아카이브한다.
|
||||
- [ ] `.gitignore` Agent-Ops 관리 block을 확인한다.
|
||||
- [ ] PASS이면 template 기준 `complete.log`를 작성하고 active `.md`를 남기지 않는다.
|
||||
- [ ] PASS이면 task directory를 월별 archive로 이동한다.
|
||||
- [ ] PASS이면 milestone 완료 이벤트를 보고하고 roadmap/update-roadmap을 직접 호출하지 않는다.
|
||||
- [ ] 마지막 split task archive 후 빈 active parent를 제거하거나 남은 sibling/file을 확인한다.
|
||||
- [ ] WARN/FAIL이면 다음 filesystem state를 작성하고 `complete.log`를 만들지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 deviation과 이유를 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 실제 결정을 기록한다._
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- omitted config가 on, explicit false가 off이며 request 중간 config 변경이 이미 시작한 request를 바꾸지 않는가.
|
||||
- Chat/Responses, legacy/gated/tunnel 모두 on raw fields/off field absence와 nonraw continuity가 일치하는가.
|
||||
- prompt/message/tool args/result/auth/provider token은 on에서도 observation/general log/review evidence에 없는가.
|
||||
- Core observation은 raw-output toggle과 무관하게 raw-free이고 stable correlation/filter/rule/fingerprint/count/offset/release reason을 갖는가.
|
||||
- raw HTTP/OpenAI SDK-equivalent caller가 같은 decision을 내고 caller 제품명이 condition이 아닌가.
|
||||
- live source/build/runtime identity가 증명되고 5 concurrent × 3 evidence가 sanitized 형태이며 raw artifact는 ignored path에만 있는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### Dependency
|
||||
|
||||
```bash
|
||||
test -f agent-task/m-openai-compatible-output-validation-filters/04+03_schema_contract/complete.log
|
||||
```
|
||||
|
||||
_실제 출력/종료 코드를 기록한다._
|
||||
|
||||
### Local
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'Test(OpenAILog|OutputFilter|ProviderObservability)'
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
_실제 stdout/stderr와 종료 코드를 기록한다._
|
||||
|
||||
### Dev preflight/smoke
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
git rev-parse HEAD
|
||||
git fetch origin
|
||||
git rev-parse origin/main
|
||||
go version && go env GOMOD
|
||||
iop-edge --help
|
||||
iop-edge version
|
||||
go test ./apps/edge/...
|
||||
go test ./packages/go/... ./proto/gen/...
|
||||
iop-edge smoke openai --base-url http://127.0.0.1:18083
|
||||
IOP_OPENAI_BASE_URL=http://127.0.0.1:18083 IOP_OPENAI_MODEL=ornith:35b IOP_OPENAI_SMOKE_CONCURRENCY=5 IOP_OPENAI_SMOKE_RUNS=3 IOP_OPENAI_SMOKE_PROMPT_FILE=agent-test/runs/output-filter-recovery/repeat-prompt.txt IOP_OPENAI_SMOKE_ARTIFACT_DIR=agent-test/runs/output-filter-recovery/ops-evidence go test -count=1 -v ./apps/edge/internal/openai -run TestDevOutputFilterOpsEvidence
|
||||
```
|
||||
|
||||
_runner/repo/source/build/config/runtime/provider identity, on/off results, sanitized reproduced/not_reproduced evidence를 기록한다. raw/token은 붙이지 않는다._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## 섹션 소유권
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute archive/complete procedures |
|
||||
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify |
|
||||
| Archive Evidence Snapshot | Fixed when present | Only cited archive evidence may be read |
|
||||
| Agent UI Completion | Mixed | Not applicable here |
|
||||
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks only |
|
||||
| 구현 체크리스트 | Fixed at stub creation | Implementing agent checks only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 | Fixed headings/commands | Implementing agent fills actual/sanitized output |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
<!-- task=m-openai-compatible-output-validation-filters/05+04_ops_evidence plan=0 tag=OFR-OPS -->
|
||||
|
||||
# Output Filter Recovery: 운영 evidence와 raw output logging policy
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
선행 task의 `complete.log`를 확인한 뒤 구현한다. 완료 전 `CODE_REVIEW-cloud-G09.md`의 구현 소유 섹션에 실제 변경/검증/sanitized live evidence를 채우고 active pair를 유지한 채 review-ready로 보고한다. verdict/archive/`complete.log`는 code-review skill 전용이다. blocker는 정확한 명령/출력/재개 조건만 기록하고 사용자 질문, user-input, stop 파일, 다음 상태 분류를 하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Core observation sink는 raw-free allowlist를 제공하지만 semantic filters의 운영 축과 endpoint 실행 로그를 함께 검증하는 evidence가 없다. 기존 Chat/tunnel 로그는 assembled content/reasoning을 항상 기록하고 Responses는 길이만 기록해 D03의 request-start `on|off` 정책도 일관되지 않다. 이 작업은 모든 선행 filter를 generic caller smoke로 통합하고 raw output logging toggle과 sanitized incident evidence를 고정한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
- Task ids:
|
||||
- `ops-evidence`: correlated raw-free filter evidence와 configurable assembled output logging
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- 규칙/테스트: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`, `agent-ops/rules/project/domains/edge.md`, `agent-ops/rules/project/domains/platform-common.md`, `agent-ops/rules/project/domains/testing.md`, `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`, `agent-test/dev/rules.md`, `agent-test/dev/edge-smoke.md`, `agent-test/dev/platform-common-smoke.md`, `agent-test/dev/testing-smoke.md`
|
||||
- 로드맵/설계/계약: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`, `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md`, `agent-roadmap/sdd/knowledge-tool-optimization-extension/openai-compatible-output-validation-filters/SDD.md`, `agent-contract/index.md`, `agent-contract/outer/openai-compatible-api.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/stream-evidence-gate.md`, `agent-spec/input/openai-compatible-surface.md`, `agent-spec/runtime/provider-pool-config-refresh.md`
|
||||
- 소스: `packages/go/config/edge_types.go`, `apps/edge/internal/openai/filter_observation_sink.go`, `apps/edge/internal/openai/provider_observation.go`, `apps/edge/internal/openai/chat_completion.go`, `apps/edge/internal/openai/buffered_sse.go`, `apps/edge/internal/openai/responses_completion.go`, `apps/edge/internal/openai/responses_stream_gate.go`, `apps/edge/internal/openai/provider_tunnel.go`, `apps/edge/internal/openai/chat_handler.go`, `apps/edge/internal/openai/responses_handler.go`, `apps/edge/internal/openai/stream_gate_runtime.go`, `apps/edge/internal/openai/stream_gate_filters.go`, `configs/edge.yaml`, `go.mod`
|
||||
- 테스트: `packages/go/config/stream_evidence_gate_config_test.go`, `apps/edge/internal/openai/filter_observation_sink_test.go`, `apps/edge/internal/openai/provider_observability_test.go`, `apps/edge/internal/openai/log_safety_test.go`, `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`, `apps/edge/internal/openai/stream_gate_filters_test.go`
|
||||
|
||||
### SDD 기준
|
||||
|
||||
- 승인 SDD 대상 S07 → `ops-evidence`.
|
||||
- Evidence Map은 generic raw HTTP/OpenAI SDK smoke, `ornith:35b` 5 concurrent × 3 live runs, optional Pi, D03 raw output `on|off`, role/history/provider/repeat/provider-error/schema axes와 raw prompt/tool args/result/auth 상시 제외를 요구한다.
|
||||
- deterministic S03/S05/S15 fixture가 live `not_reproduced`보다 우선하며 live raw SSE/output은 ignored `agent-test/runs/**`에만 둔다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local + dev`. local fresh tests는 `-count=1`; edge/platform-common/testing smoke, `git diff --check`, `make test`를 적용한다.
|
||||
- dev runner workdir=`/Users/toki/agent-work/iop-dev`. branch/HEAD/dirty/source sync, `go version`/GOMOD, binary help/version, config `build/dev-runtime/edge.yaml`, Edge identity/port 18083, provider host OS/arch, `ornith:35b` capacity=4를 확인한다.
|
||||
- clean `origin/main`과 같은 source/build identity로 participating environment 전체를 rebuild/redeploy/restart한 뒤 실행한다. stale/dirty/divergent 상태에서는 evidence를 만들지 않는다.
|
||||
- provider/IOP token은 remote environment에서만 읽고 명령 출력, review, tracked 문서에 표시하지 않는다. raw prompt/SSE/output은 ignored artifact dir에만 저장한다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `provider_observability_test.go`는 assembled fields가 항상 존재하는지만 검증하고 request-start on/off snapshot/Responses/gated tunnel matrix가 없다.
|
||||
- `log_safety_test.go`는 forbidden preview/raw keys를 검증하지만 raw-output-on에서 prompt/tool/auth 제외, raw-output-off에서 assembled fields 제거와 비원문 field 유지가 없다.
|
||||
- observation sink test는 raw-free allowlist를 검증하지만 repeat/history/provider/schema의 stable filter/rule/fingerprint/count/offset/release reason correlation matrix가 없다.
|
||||
- generic raw HTTP/OpenAI SDK와 live capacity+1 evidence harness가 없다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- rename/remove 없음.
|
||||
- `logChatCompletionOutput`은 legacy/runtime JSON 경로에서 공유되고, buffered SSE/tunnel/Responses는 별도 log call site다. 정책 인자를 추가할 때 모든 호출점을 compile-time 갱신한다.
|
||||
- `EdgeOpenAIConf`는 config load/restart snapshot 전반에서 값 복사되므로 pointer default helper를 통해 omitted=true와 explicit false를 구분한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- 안정 계약: “request 시작에 고정된 default-on toggle이 assembled output/reasoning 원문만 제어하고, filter observation과 비원문 운영 축은 항상 같은 correlation으로 남으며 prompt/tool args/result/auth는 어느 모드에서도 기록되지 않는다.”
|
||||
- predecessor `04+03_schema_contract`가 PASS해야 repeat/provider/schema를 함께 smoke할 수 있다. 현재 `complete.log`가 없어 `missing`.
|
||||
- 이 패킷은 output-filter-recovery의 마지막 integration/evidence consumer이며 후속 sibling dependency는 없다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 기존 로그 삭제/retention migration은 D03 범위 밖이다.
|
||||
- Pi TUI는 optional이므로 필수 PASS gate에 넣지 않는다.
|
||||
- cross-project incident auto-remediation/platform은 SDD D06의 별도 프로젝트라 제외한다.
|
||||
- metric label에는 fingerprint 원문/high-cardinality/raw payload를 추가하지 않는다.
|
||||
- 외부 dependency를 추가하지 않는다.
|
||||
|
||||
### 최종 라우팅
|
||||
|
||||
- evaluation_mode=`first-pass`; finalizer=`finalize-task-policy.sh pair`.
|
||||
- build closure: scope=2, state=1, blast=2, evidence=2, verification=2 → G09; base/final=`grade-boundary`, lane=`cloud`, filename=`PLAN-cloud-G09.md`.
|
||||
- review closure: 같은 G09, route=`official-review`, lane=`cloud`, filename=`CODE_REVIEW-cloud-G09.md`.
|
||||
- `large_indivisible_context=false`.
|
||||
- loop risks: `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product` (4).
|
||||
- recovery: `review_rework_count=0`, `evidence_integrity_failure=false`.
|
||||
- capability gap: 없음.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [OFR-OPS-1] default-on/request-start assembled output logging policy를 Chat/Responses/legacy/gated/tunnel 전 경로에 적용하고 on/off/always-redacted 테스트를 추가한다.
|
||||
- [ ] [OFR-OPS-2] semantic filter observation의 stable correlation/filter/rule/fingerprint/count/offset/release-or-close axes를 generic raw HTTP/OpenAI SDK fixture로 검증한다.
|
||||
- [ ] [OFR-OPS-3] outer/inner 계약·현재 spec/config 예시를 갱신하고 dev `ornith:35b` capacity+1 × 3 sanitized evidence를 완료한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.
|
||||
|
||||
### [OFR-OPS-1] request-start raw output logging policy
|
||||
|
||||
#### 문제
|
||||
|
||||
- `packages/go/config/edge_types.go:117-134`에는 assembled output logging 설정이 없다.
|
||||
- `chat_completion.go:147-162`, `buffered_sse.go:127-132`, `provider_tunnel.go:243-253`은 content/reasoning을 항상 zap field로 기록한다.
|
||||
- Responses log는 길이만 기록해 default-on 동작도 endpoint 간 일관되지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`openai.log_assembled_output: *bool`을 추가하고 omitted=`true`, explicit false=`off`로 해석한다. 각 inbound request 시작 시 effective bool을 request context/dispatch snapshot에 저장한다. true면 Chat/Responses/tunnel의 assembled content/reasoning을 기록하고 false면 해당 field 자체를 생략하되 lengths, role/channel, model/provider, attempt/decision 같은 비원문 fields는 유지한다. live config가 바뀌어도 이미 시작한 request snapshot은 바뀌지 않는다.
|
||||
|
||||
Before (`apps/edge/internal/openai/chat_completion.go:149`):
|
||||
|
||||
```go
|
||||
s.logger.Info("openai chat completion output",
|
||||
...
|
||||
zap.String("assembled_content", result.message.Content),
|
||||
zap.String("assembled_reasoning", result.message.ReasoningContent),
|
||||
)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
fields := openAIOutputLogFields(result, requestLogPolicy)
|
||||
s.logger.Info("openai chat completion output", fields...)
|
||||
```
|
||||
|
||||
helper는 raw user prompt, message, tool args/result, authorization/provider-auth를 입력으로 받지 않는다. tool call names/count는 non-raw operational field로 유지하되 args/result는 금지한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/go/config/edge_types.go`: pointer setting, `EffectiveLogAssembledOutput()`.
|
||||
- [ ] `apps/edge/internal/openai/chat_handler.go`: request-start snapshot.
|
||||
- [ ] `apps/edge/internal/openai/responses_handler.go`: request-start snapshot.
|
||||
- [ ] `apps/edge/internal/openai/chat_completion.go`: normalized/legacy Chat conditional fields.
|
||||
- [ ] `apps/edge/internal/openai/buffered_sse.go`: buffered stream conditional fields.
|
||||
- [ ] `apps/edge/internal/openai/responses_completion.go`: Responses default-on/explicit-off fields.
|
||||
- [ ] `apps/edge/internal/openai/responses_stream_gate.go`: gated Responses fields.
|
||||
- [ ] `apps/edge/internal/openai/provider_tunnel.go`: Chat/Responses tunnel snapshot/conditional fields.
|
||||
- [ ] `packages/go/config/stream_evidence_gate_config_test.go`: omitted true/explicit true/false decode.
|
||||
- [ ] `apps/edge/internal/openai/provider_observability_test.go`: endpoint/path on/off matrix.
|
||||
- [ ] `apps/edge/internal/openai/log_safety_test.go`: field presence/absence와 always-redacted sentinels.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestOpenAILogAssembledOutputDefaultsOn`, `TestOpenAILogAssembledOutputOffAllPaths`, `TestOpenAILogPolicyIsRequestStable`, `TestOpenAILogNeverIncludesPromptToolPayloadOrAuth`.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/go/config ./apps/edge/internal/openai -run 'TestOpenAI(LogAssembled|LogPolicy|LogNever|ProviderObservability)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, off request에서 assembled raw fields 0개, nonraw fields 유지.
|
||||
|
||||
### [OFR-OPS-2] filter observation correlation과 generic fixture
|
||||
|
||||
#### 문제
|
||||
|
||||
- `filter_observation_sink.go:48-170`은 attribution/hold/recovery/evidence allowlist를 제공하지만 semantic filter들이 동일 correlation에서 필요한 axes를 채우는 end-to-end test가 없다.
|
||||
- release/terminal/idle close 이유와 provider re-admission/schema result가 generic caller fixture에 연결되지 않는다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
repeat/history/provider/schema filter가 이미 만든 sanitized evidence descriptor를 stable rule id와 함께 사용하고 sink의 allowlist field를 end-to-end로 assert한다. generic raw HTTP와 OpenAI SDK-equivalent request body는 동일 registry/config에서 같은 decision을 내야 하며 caller product field를 판정에 넣지 않는다.
|
||||
|
||||
Before (`apps/edge/internal/openai/filter_observation_sink.go:156`):
|
||||
|
||||
```go
|
||||
if ev := obs.Evidence(); ev != nil {
|
||||
fields = append(fields,
|
||||
zap.String("evidence_filter_rule", ev.FilterRule()),
|
||||
zap.String("evidence_fingerprint", fpHex),
|
||||
zap.Int("evidence_count", ev.Count()),
|
||||
zap.Int("evidence_offset", ev.Offset()),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
// Existing raw-free fields remain the contract. Semantic fixtures must populate
|
||||
// stable attribution/evidence and descriptor-based release-or-close reasons.
|
||||
```
|
||||
|
||||
production sink는 raw fields를 새로 추가하지 않는다. 필요한 변경은 filter descriptor/reason 누락을 보완하는 최소 범위와 tests에 한정한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_filters.go`: stable rule/descriptor/fingerprint/count/offset 누락 보완.
|
||||
- [ ] `apps/edge/internal/openai/filter_observation_sink.go`: 기존 allowlist에서 필요한 nonraw release/close field가 실제 Core accessor에 있을 때만 추가.
|
||||
- [ ] `apps/edge/internal/openai/filter_observation_sink_test.go`: repeat/history/provider/schema correlation matrix와 forbidden raw sentinels.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: raw HTTP/OpenAI SDK-equivalent repeat/schema/provider fixtures, provider switch, terminal/idle/recovery sequence.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 작성: `TestOutputFilterObservationCorrelationMatrix`, `TestOutputFilterGenericCallerParity`, `TestOutputFilterObservationRawFree`.
|
||||
- prompt/tool args/result/auth/output sentinel을 context/event source에 넣고 observation JSON에 0건인지 검증한다. raw output은 일반 execution log policy만 제어하며 Core observation에는 어느 모드에서도 넣지 않는다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'TestOutputFilter(Observation|GenericCaller)'
|
||||
```
|
||||
|
||||
기대 결과: PASS, same correlation/attempt transition과 raw-free invariant.
|
||||
|
||||
### [OFR-OPS-3] 계약 동기화와 dev evidence
|
||||
|
||||
#### 문제
|
||||
|
||||
- outer/inner contracts와 config example에 raw output toggle 이름/default/request-start semantics가 없다.
|
||||
- S07의 generic caller 및 Korean live evidence가 아직 구현 결과와 연결되지 않았다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`openai.log_assembled_output`, on/off 이후 request semantics, raw prompt/tool/auth 상시 제외, existing-log non-deletion, sanitized evidence axes를 계약/spec/config에 반영한다. dev env-gated harness는 ignored prompt/artifact path를 사용해 `ornith:35b` capacity 4 + 1 = 5 concurrent requests를 3 runs 수행한다. actual repeat면 fingerprint/offset/guard/recovery, 미재현이면 `not_reproduced`; provider error/schema path는 stable matcher/validation reason과 shared attempt/commit/pool selection을 기록한다.
|
||||
|
||||
Before (`packages/go/config/edge_types.go:117`):
|
||||
|
||||
```go
|
||||
type EdgeOpenAIConf struct {
|
||||
Enabled bool
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
type EdgeOpenAIConf struct {
|
||||
Enabled bool
|
||||
LogAssembledOutput *bool `mapstructure:"log_assembled_output" yaml:"log_assembled_output,omitempty"`
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `configs/edge.yaml`: default-on/false example과 sensitive-data warning.
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: logging/evidence caller-visible privacy boundary.
|
||||
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`: config default/request snapshot/restart classification.
|
||||
- [ ] `agent-spec/runtime/stream-evidence-gate.md`: semantic observation axes.
|
||||
- [ ] `agent-spec/input/openai-compatible-surface.md`: endpoint logging/privacy.
|
||||
- [ ] `agent-spec/runtime/provider-pool-config-refresh.md`: config snapshot/current behavior.
|
||||
- [ ] `apps/edge/internal/openai/stream_gate_vertical_slice_test.go`: env-gated S07 live entry 또는 동일 profile-compatible harness.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- local generic fixture는 필수.
|
||||
- dev harness는 `IOP_OPENAI_SMOKE_PROMPT_FILE`과 `IOP_OPENAI_SMOKE_ARTIFACT_DIR`만 raw artifact source/sink로 사용하고 credential/raw output을 test log에 출력하지 않는다.
|
||||
- Pi field smoke는 가능하면 별도 evidence로 기록하되 미실행은 PASS를 막지 않는다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
기대 결과: PASS.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
- predecessor: `04+03_schema_contract`.
|
||||
- 시작 조건: `agent-task/m-openai-compatible-output-validation-filters/04+03_schema_contract/complete.log`.
|
||||
- 현재 상태: `missing`. directory `05+04_ops_evidence`의 `+04` 외 추가 runtime dependency는 없다.
|
||||
- OFR-OPS-1 → 2 → 3 순서로 구현한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `packages/go/config/edge_types.go` | OFR-OPS-1/3 |
|
||||
| `apps/edge/internal/openai/chat_handler.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/responses_handler.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/chat_completion.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/buffered_sse.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/responses_completion.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/responses_stream_gate.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/provider_tunnel.go` | OFR-OPS-1 |
|
||||
| `packages/go/config/stream_evidence_gate_config_test.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/provider_observability_test.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/log_safety_test.go` | OFR-OPS-1 |
|
||||
| `apps/edge/internal/openai/stream_gate_filters.go` | OFR-OPS-2 |
|
||||
| `apps/edge/internal/openai/filter_observation_sink.go` | OFR-OPS-2 |
|
||||
| `apps/edge/internal/openai/filter_observation_sink_test.go` | OFR-OPS-2 |
|
||||
| `apps/edge/internal/openai/stream_gate_vertical_slice_test.go` | OFR-OPS-2/3 |
|
||||
| `configs/edge.yaml` | OFR-OPS-3 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | OFR-OPS-3 |
|
||||
| `agent-contract/inner/edge-config-runtime-refresh.md` | OFR-OPS-3 |
|
||||
| `agent-spec/runtime/stream-evidence-gate.md` | OFR-OPS-3 |
|
||||
| `agent-spec/input/openai-compatible-surface.md` | OFR-OPS-3 |
|
||||
| `agent-spec/runtime/provider-pool-config-refresh.md` | OFR-OPS-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
Local:
|
||||
|
||||
```bash
|
||||
go version && go env GOMOD
|
||||
git diff --check
|
||||
go test -count=1 ./apps/edge/internal/openai -run 'Test(OpenAILog|OutputFilter|ProviderObservability)'
|
||||
go test -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai
|
||||
make test
|
||||
```
|
||||
|
||||
Dev preflight/smoke, `/Users/toki/agent-work/iop-dev`:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
git rev-parse HEAD
|
||||
git fetch origin
|
||||
git rev-parse origin/main
|
||||
go version && go env GOMOD
|
||||
iop-edge --help
|
||||
iop-edge version
|
||||
go test ./apps/edge/...
|
||||
go test ./packages/go/... ./proto/gen/...
|
||||
iop-edge smoke openai --base-url http://127.0.0.1:18083
|
||||
IOP_OPENAI_BASE_URL=http://127.0.0.1:18083 IOP_OPENAI_MODEL=ornith:35b IOP_OPENAI_SMOKE_CONCURRENCY=5 IOP_OPENAI_SMOKE_RUNS=3 IOP_OPENAI_SMOKE_PROMPT_FILE=agent-test/runs/output-filter-recovery/repeat-prompt.txt IOP_OPENAI_SMOKE_ARTIFACT_DIR=agent-test/runs/output-filter-recovery/ops-evidence go test -count=1 -v ./apps/edge/internal/openai -run TestDevOutputFilterOpsEvidence
|
||||
```
|
||||
|
||||
기대 결과: local 전부 PASS. dev는 clean/synced same build, config `build/dev-runtime/edge.yaml`, Edge port 18083, `ornith:35b` capacity 4를 확인하고 5 concurrent × 3 runs를 완료한다. tracked/review evidence에는 raw prompt/SSE/output/tool args/result/auth/token 없이 stable model/provider/attempt/filter/rule/fingerprint/count/offset/decision과 `reproduced|not_reproduced`만 남긴다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
|
||||
Loading…
Reference in a new issue