<!-- task=m-iop-owned-single-request-agent-execution/16+14,15_observation_evidence plan=2 tag=REVIEW_REVIEW_API milestone-task=cleanup-observation -->

# Close the Single-request Observation Allowlist Evidence

## For the Implementing Agent

Resolve Required finding R1 exactly as mapped below. Strengthen only the real-POST observation test, run every verification command with fresh test execution, fill `CODE_REVIEW-cloud-G03.md` with literal notes and stdout/stderr, then leave both active files in place and report ready for review. Do not change production code, metrics/log schemas, API or wire contracts, living specs, or external Claude/Mac qualification scope. If blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields; do not ask the user, call user-input tools, create stop-state files, classify the next state, archive logs, or write `complete.log`.

## Background

The second implementation connected the real Anthropic POST to the production observation adapter and proved the expected lifecycle deltas, shared correlation, and selected privacy sentinels. Review found that its metric parser ignores unknown label names and its log loop never verifies the exact field schema or closed lifecycle tuple multiset. The test therefore cannot yet serve as the SDD S07 metric/log allowlist evidence claimed by the living specs.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-owned-single-request-agent-execution/16+14,15_observation_evidence/plan_cloud_G05_1.log`
- Prior review: `agent-task/m-iop-owned-single-request-agent-execution/16+14,15_observation_evidence/code_review_cloud_G05_1.log`
- Verdict: `FAIL` with Required finding R1.
- R1: `snapshotSingleRequestMetrics` ignores unknown metric labels, and the captured-log loop does not enforce the production field allowlist or expected closed event/stage/operation/outcome/error tuples.
- R2 from the preceding loop is closed: `agent-spec/input/openai-compatible-surface.md` is `status: 부분`, and the stale runtime cleanup-observation deferral is removed while provider-driver and actual Claude/Mac qualification remain deferred.
- Fresh verification evidence: both predecessor checks, the focused real-POST test, the full OpenAI package, focused service observation tests, vet, deterministic spec checks, and `git diff --check` pass. The remaining defect is an assertion gap proven by direct inspection, not a production failure.
- Roadmap carryover: preserve `milestone-task=cleanup-observation`; deterministic S07 allowlist evidence remains in scope and actual Claude/Mac timing qualification remains deferred to S12 `claude-smoke`.

## Finding Resolution Map

| Finding | Disposition | Direct-fix Targets | Verified Current Evidence | Changed Precondition |
|---------|-------------|--------------------|---------------------------|----------------------|
| R1 | direct-fix | `apps/edge/internal/openai/single_request_handler_test.go` | Lines 524-536 and 543-555 parse only known labels and discard unknown names; lines 715-741 check message/correlation/sentinels but not exact log fields or lifecycle tuples. | Both lifecycle metric families reject missing, duplicate, or unknown labels, and every production log entry is validated against the exact field allowlist, types, and expected lifecycle tuple multiset before the same fresh real-POST test is rerun. |

## Analysis

### Files Read

- `agent-task/m-iop-owned-single-request-agent-execution/16+14,15_observation_evidence/plan_cloud_G05_1.log`
- `agent-task/m-iop-owned-single-request-agent-execution/16+14,15_observation_evidence/code_review_cloud_G05_1.log`
- `apps/edge/internal/openai/single_request_handler_test.go`
- `apps/edge/internal/service/single_request_metrics.go`
- `apps/edge/internal/service/single_request_metrics_test.go`
- `apps/edge/internal/service/single_request_observation.go`
- `apps/edge/internal/service/single_request_observation_test.go`
- `apps/edge/internal/bootstrap/single_request_observation_test.go`
- `agent-spec/index.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-contract/index.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`; status `[승인됨]`, lock released, no `USER_REVIEW.md`.
- First-line scope: `milestone-task=cleanup-observation`.
- Targeted scenario: S07 `cleanup-observation`.
- Evidence Map driver: S07 requires cleanup-race, user-result-preservation, and raw-free timing/log/metric allowlist coverage. This packet closes only the linked production-adapter metric/log allowlist portion; predecessor packets retain cleanup and timing ownership.
- The implementation checklist therefore requires exact metric label names, exact log fields/types, and exact closed lifecycle tuples from the same real POST, while final verification preserves the existing S07 package and spec checks.

### Verification Context

- No separate neutral verification handoff was supplied. Repository-native sources were the active/archived loop artifacts, local test rules, Edge domain rules, the production observer, its unit/integration tests, the living specs, the Anthropic contract, and SDD S07.
- Predecessors are uniquely complete at `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/complete.log` and `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/complete.log`.
- Fresh commands already pass for the focused OpenAI test, full OpenAI package, focused service observation tests, vet, spec status/deferral checks, deterministic spec search, and whitespace.
- Constraints: preserve unrelated dirty-worktree changes; do not change production projection schemas or specs; use `-count=1` for fresh Go evidence; do not use dispatcher or external provider execution.
- External verification preflight: not applicable. Actual Claude/Mac timing is an explicit S12 exclusion and no external runner, credential, port, or device is required for this deterministic test repair.
- Gap: the existing integration assertions do not fail on an added lifecycle metric label and do not prove the production log field/value allowlist. Confidence: high, based on direct source inspection and fresh passing commands.

### Test Coverage Gaps

- Covered: production observer installation, exact request/stage/tool/cleanup/terminal counter and histogram sample-count deltas, shared bounded correlation, ingress unlabeledness, public-terminal privacy, selected raw log sentinels, and R2 spec consistency.
- Missing: exact label-name set validation for both lifecycle metric families. Unknown or duplicate labels are currently ignored by the snapshot helper.
- Missing: exact production log key/type validation and the expected closed lifecycle tuple multiset for the real POST. Entry count alone cannot detect a wrong or duplicated event tuple.

### Symbol References

- No production symbol is renamed or removed.
- `snapshotSingleRequestMetrics` and `singleRequestMetricKey` are test-local and referenced only in `apps/edge/internal/openai/single_request_handler_test.go`.
- `SetSingleRequestObservationLogger` remains production-owned in `apps/edge/internal/service/single_request_metrics.go` and bootstrap wiring remains unchanged.

### Split Judgment

- Keep one atomic packet. Metric and log allowlists are two projections of one production observation DTO and must be proven by the same real POST and expected lifecycle multiset.
- Runtime predecessors decoded from `16+14,15_observation_evidence` are satisfied by the unique packet 14 and packet 15 archive `complete.log` paths listed above.
- No sibling split is useful because either isolated assertion change would leave S07 allowlist evidence incomplete.

### Scope Rationale

- Include only `apps/edge/internal/openai/single_request_handler_test.go` plus the active review evidence file.
- Exclude production service/metric/log code because fresh evidence shows an integration-test oracle defect, not a runtime defect.
- Exclude living-spec edits because R2 is already closed and the intended documented boundary remains correct once R1 is fully asserted.
- Exclude API/wire contracts, Node behavior, dashboards, external smoke, and roadmap mutation.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer `finalize-task-policy.sh` in `pair` mode returned `status=routed`.
- Build closures: scope/context/verification/evidence/ownership/decision are all true. Scores `scope=1`, `state=0`, `blast=0`, `evidence=1`, `verification=1` produce G03 with base `local-fit`.
- Build signals: `large_indivisible_context=false`; matched loop-risk signature `boundary_contract`; `loop_risk_count=1`; `review_rework_count=2`; `evidence_integrity_failure=true`; `risk_boundary_matched=false`; `recovery_boundary_matched=true`.
- Build route: `recovery-boundary`, lane `cloud`, canonical filename `PLAN-cloud-G03.md`, catalog route `worker/cloud/G03`.
- Review closures are all true with scores `1/0/0/1/1`; route `official-review`, lane `cloud`, G03, canonical filename `CODE_REVIEW-cloud-G03.md`, catalog route `review/cloud/G03`.

## Dependencies and Execution Order

1. Preserve packet 14 timing semantics and packet 15 production observation adapters, satisfied by their exact archived `complete.log` evidence.
2. Repair the metric snapshot label oracle before relying on lifecycle delta assertions.
3. Repair the captured-log schema and tuple oracle while retaining shared-correlation and privacy assertions.
4. Run all fresh verification and record literal stdout/stderr.

## Implementation Checklist

- [ ] Resolve R1 by making both lifecycle metric snapshots reject missing, duplicate, or unexpected label names while retaining exact counter and histogram delta assertions.
- [ ] Require every captured production observation log to have the exact field allowlist and types, the expected closed lifecycle tuple multiset, one shared bounded generated correlation, and no private sentinels.
- [ ] Keep production lifecycle behavior, metric/log schemas, API/wire contracts, Node behavior, and the already-correct living-spec status/deferral text unchanged.
- [ ] Run every dependency, focused, package, service-observation, vet, deterministic spec-consistency, and whitespace command with cache disabled where specified.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Implementation Plan

### [REVIEW_REVIEW_API-1] Enforce the production projection allowlists

**Problem**

- `apps/edge/internal/openai/single_request_handler_test.go:524-536` and `:543-555` recognize five lifecycle label names but have no rejection path for any other name, missing label, or duplicate label. A new `request_id`, correlation, provider, or content-derived label would be silently discarded and the expected key could still pass.
- `apps/edge/internal/openai/single_request_handler_test.go:715-741` verifies eight log entries, a common bounded correlation, and selected private sentinels, but it never checks the exact nine production keys or the expected request/stage/tool/cleanup/terminal tuple counts.

**Solution**

Replace permissive label extraction with a test helper that accepts exactly one each of `event_class`, `stage`, `operation`, `outcome`, and `error_class`, rejects all other/missing/duplicate names for both lifecycle metric families, and returns the same `singleRequestMetricKey` used by delta assertions.

Before (`apps/edge/internal/openai/single_request_handler_test.go:524`):

```go
for _, lp := range m.GetLabel() {
	switch lp.GetName() {
	case "event_class":
		key.eventClass = lp.GetValue()
	// Unknown labels are currently ignored.
	}
}
```

After:

```go
key, err := singleRequestMetricKeyFromLabels(m.GetLabel())
if err != nil {
	return nil, nil, err
}
```

The helper must verify the exact five-name set and uniqueness before returning the key. Then derive one `singleRequestMetricKey` from each captured log's closed string fields, require the exact production key set (`correlation`, five lifecycle fields, `duration_ms`, `tool_count`, `has_result`) and types, and compare the log-key counts with `wantDeltas`. Retain the correlation bound/generated-shape check and the existing public/log sentinel checks.

Before (`apps/edge/internal/openai/single_request_handler_test.go:719`):

```go
ctxMap := entry.ContextMap()
corrVal, ok := ctxMap["correlation"].(string)
// Only correlation and selected private sentinels are checked.
```

After:

```go
key, correlation, err := singleRequestLogKey(entry.ContextMap())
if err != nil {
	t.Fatalf("log[%d] schema: %v", i, err)
}
logCounts[key]++
```

The new helper remains test-local and must not duplicate or modify production behavior.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/openai/single_request_handler_test.go` — reject metric label drift and assert the exact production log schema/lifecycle tuple multiset in `TestAnthropicSingleRequestObservation`.

**Test Strategy**

- Extend `TestAnthropicSingleRequestObservation`; do not add a fake observer path. The regression oracle must remain connected to `SetSingleRequestObservationLogger`, the default Prometheus collectors, the real handler/coordinator/workspace tool loop, cleanup, and terminal acknowledgement.
- Keep before/after snapshots because the collectors are process-global. Validate family label schemas before map insertion so unknown or duplicate dimensions cannot collapse into a valid key.
- Count captured log tuples and compare them with the same expected lifecycle multiset used for metrics, while separately validating exact keys/types, generated correlation, and raw-free sentinels.

**Verification**

- `go test ./apps/edge/internal/openai -run 'TestAnthropicSingleRequestObservation' -count=1`
- `go test ./apps/edge/internal/service -run 'TestSingleRequestMetrics|TestSingleRequestObservationLifecycleIntegration' -count=1`
- Expected: the real POST proves identical closed metric/log lifecycle projections and fails on any unknown label, unexpected log field/type, wrong lifecycle tuple, correlation divergence, or private sentinel.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/openai/single_request_handler_test.go` | REVIEW_REVIEW_API-1 / R1 |
| `agent-task/m-iop-owned-single-request-agent-execution/16+14,15_observation_evidence/CODE_REVIEW-cloud-G03.md` | REVIEW_REVIEW_API-1 evidence |

## Final Verification

1. `test -f agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/complete.log' | wc -l)" -eq 1`
2. `test -f agent-task/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/complete.log' | wc -l)" -eq 1`
3. `go test ./apps/edge/internal/openai -run 'TestAnthropicSingleRequestObservation' -count=1`
4. `go test ./apps/edge/internal/openai -count=1`
5. `go test ./apps/edge/internal/service -run 'TestSingleRequestMetrics|TestSingleRequestObservationLifecycleIntegration' -count=1`
6. `go vet ./apps/edge/internal/openai`
7. `test "$(sed -n 's/^status: //p' agent-spec/input/openai-compatible-surface.md | head -n 1)" = "부분"`
8. `! rg --fixed-strings 'Those drivers, raw-free cleanup observation, and actual Claude qualification remain separate work' agent-spec/runtime/edge-node-execution.md`
9. `git diff --check`

Expected: both predecessors remain uniquely complete; the production-adapter real POST enforces exact lifecycle metric/log allowlists and deltas; the affected package, related service observation tests, and vet pass; the closed R2 spec state remains intact; whitespace is clean. Cached Go test results are not acceptable.

**After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.**
