<!-- task=glm_coding_plan plan=0 tag=API -->

# Plan - GLM General API와 Coding Plan Profile 분리

## For the Implementing Agent

> `CODE_REVIEW-cloud-G06.md`의 implementation-owned section 작성은 구현의 필수 마지막 단계다.
> 모든 검증을 실행하고 실제 구현 메모와 stdout/stderr를 채운 뒤 active PLAN/CODE_REVIEW 파일을 그대로 두고 review 준비 완료를 보고한다.
> 최종 판정, archive, `complete.log` 작성은 code-review skill만 수행한다.
> 차단되면 implementation-owned evidence field에 정확한 blocker, 시도한 명령/출력, 재개 조건만 기록한다. 사용자에게 질문하거나 user-input tool/control-plane stop file을 만들거나 다음 상태를 분류하지 않는다.

## Background

현재 built-in `glm` profile은 일반 종량제 endpoint만 가리키고 공식 function calling capability도 선언하지 않는다. Z.AI Coding Plan은 같은 Bearer/OpenAI Chat 계약을 사용하지만 별도 `/api/coding/paas/v4` endpoint와 구독 quota를 사용하므로 endpoint override만으로 합치면 과금 경로와 managed credential route가 모호해진다. `glm`과 `glm_coding`을 독립 profile로 유지하고 Edge 외부 model id도 provider별로 분리해 일반 API/Coding Plan 사이 자동 fallback이나 pool 혼합이 발생하지 않게 한다. Coding Plan은 기본 활성화하지 않으며 공식 지원 도구·개인 사용·proxy 제한을 운영 예시와 smoke에 명시한다.

## Analysis

### Files Read

- `AGENTS.md`
- `.gitignore`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/control-plane/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/update-test/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `agent-ops/skills/common/plan/templates/review-stub-template.md`
- `agent-roadmap/ROADMAP.md`
- `agent-roadmap/current.md`
- `agent-roadmap/priority-queue.md`
- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`
- `agent-spec/index.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-contract/index.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/control-plane-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/edge-smoke.md`
- `packages/go/config/protocol_profile.go`
- `packages/go/config/protocol_profile_test.go`
- `apps/control-plane/internal/credentialstore/route.go`
- `apps/control-plane/internal/credentialstore/route_test.go`
- `apps/node/internal/adapters/openai_compat/provider.go`
- `apps/node/internal/adapters/openai_compat/protocol_profile_test.go`
- `configs/edge.yaml`

### SDD Criteria

not applicable. 이 작업은 현재 활성 Milestone과 무관한 기존 provider-profile/credential-route 기능의 소규모 후속이며, 사용자가 Milestone 생성이 불필요하다고 확정했다. Roadmap과 `agent-roadmap/current.md`는 수정하지 않는다.

### Verification Context

- Handoff: supplied. 사용자가 `token/.glm` credential로 Coding Plan endpoint 접속 확인, SOPS 저장, agent-test 절차 기록을 요청했고 이전 검증에서 `GET /models` 200(8 models), `glm-5.1` non-stream Chat 200, SSE 200/`[DONE]`, usage 응답을 확인했다. token 원문은 기록하지 않았다.
- update-test resolve-context:
  - Rules State: usable.
  - Sources: `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/control-plane-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/edge-smoke.md`.
  - Required repository commands: fresh `go test` for changed config/control-plane/node packages; `make build-edge`; `make test-e2e` for the touched Edge/config user path.
  - Cache: fresh required; all Go commands use `-count=1`.
  - Constraint: token/API key/private endpoint values must not appear in tracked files or stdout/stderr.
- Repository baseline:
  - `go test -count=1 ./packages/go/config ./apps/control-plane/internal/credentialstore ./apps/node/internal/adapters/openai_compat`: PASS before changes.
  - The existing adapter already resolves profile-specific `models` and `chat_completions` operations; no new driver or adapter branch is required.
- Official references reviewed on 2026-08-02:
  - `https://docs.z.ai/scenario-example/develop-tools/others` — Coding Plan base URL differs from General API.
  - `https://docs.z.ai/api-reference/introduction` — Bearer authentication and endpoint distinction.
  - `https://docs.z.ai/api-reference/llm/chat-completion` — Chat, stream, tools/tool calls.
  - `https://docs.z.ai/legal-agreement/subscription-terms` and `https://docs.z.ai/devpack/usage-policy` — supported-tool, personal-use, and proxy/sharing restrictions.
- External Verification Preflight:
  - Runner/workdir: local shell, `/config/workspace/iop-s2`.
  - Branch/HEAD/dirty: `feature/glm-coding-plan`, `32c0754f91b05ee95ab25b1062016d44fba18bf2`, clean at plan preflight.
  - Source sync: `origin/dev` is the same commit; left/right delta `0 0`.
  - OS/arch: Linux/aarch64.
  - Tools: Go `go1.26.2 linux/arm64` at `/config/opt/go/bin/go` (module minimum is Go 1.24), SOPS 3.13.1, jq 1.7, curl 8.5.0, Python 3.12.3.
  - Config/evidence paths: repository `configs/edge.yaml`; encrypted SOPS `/config/.config/iop/secrets/dev-openai-toki.sops.yaml`; age key `/config/.config/sops/age/keys.txt`.
  - Secret state: SOPS filestatus reports `encrypted=true`; both files mode `0600`; `tokens.glm-coding-plan` decrypts to a non-empty value without printing it.
  - External host: `api.z.ai:443` reachable; unauthenticated `https://api.z.ai/api/coding/paas/v4/models` returns 401.
  - Binary/artifact/ports/process: direct provider smoke needs no IOP binary or listening port. `make build-edge` and `make test-e2e` build/start their own repository-native artifacts/runtime.
  - Mismatch handling: if branch/HEAD/source sync or secret modes differ, stop and restore the exact checkout/`0600` state before live smoke. Do not copy the secret into the repository.
- Gaps: no repository implementation gap remains unidentified. A live IOP-as-proxy Coding Plan deployment is intentionally not part of completion because provider terms require an eligible supported use or separate authorization; the profile and example remain explicit opt-in.
- Confidence: high. Official endpoint/auth/tool facts, successful live upstream evidence, current code, focused baseline tests, and repository-native smoke rules agree.
- Maintenance: update-test-candidate by user request; extend the existing `edge-smoke` profile rather than creating a new test profile.

### Test Coverage Gaps

- Built-in catalog has no `glm_coding` ID, Coding Plan URL assertion, or explicit “no Responses” assertion. Add literal catalog/URL/capability tests.
- `glm` omits `tool_calling` despite the official Chat API supporting tools. Assert both GLM profiles advertise it.
- Provider-pool config has no regression proving two external model IDs select two distinct provider/profile IDs while both rewrite to `glm-5.1`. Add a YAML load test.
- Node loopback operation fixtures cover only the general `glm` URL. Add `glm_coding` across JSON, SSE, and provider-error fixture outcomes.
- Managed credential compatibility allows only `glm` for `glm/bearer`. Add a same-slot dual-profile route test including distinct aliases and exact resource selectors, plus a cross-vendor rejection.
- Local Edge smoke has no SOPS-backed Coding Plan procedure or usage-policy guard. Add the exact redacted preflight and models/chat/stream/tool checks.
- No real Edge proxy request is added to the completion gate; deterministic config/adapter/managed-route tests plus repository E2E cover IOP behavior, while direct authenticated upstream evidence covers the endpoint.

### Symbol References

none. No symbol is renamed or removed.

### Split Judgment

Single plan. The indivisible invariant is that one stable `glm_coding` profile identity must agree across the built-in catalog, managed credential allow-list, Node operation URL fixture, Edge provider/model example, spec, and smoke procedure. Splitting could leave a selectable profile without a compatible managed route or a credential route without the expected transport endpoint.

### Scope Rationale

- Do not add a protocol driver, config/schema field, protobuf message, adapter implementation, OpenAI handler branch, provider category, or automatic endpoint selection; existing `openai_chat`, `category: api`, profile resolution, and model-driven provider-pool routing already cover the behavior.
- Do not change `agent-contract/inner/edge-config-runtime-refresh.md` or `agent-contract/outer/openai-compatible-api.md`. Their stable contracts already define profile selection, exact managed route binding, external `model` routing, and no-fallback behavior; this task only adds catalog data and examples.
- Do not modify roadmap/Milestone state, SOPS/age files, `token/.glm`, or any tracked secret. The host-local encrypted token already exists and is verification input only.
- Do not add `responses` to either GLM profile, infer a model list in code, combine `glm-api` and `glm-coding` beneath one external model ID, or fall back between General API and Coding Plan.
- Do not enable the Coding Plan provider in active example config. All GLM provider/model entries remain comment-only and include the official usage restriction.

### Final Routing

- evaluation_mode: `first-pass`
- finalizer: `finalize-task-policy.sh`, mode `pair`
- Build closures: scope/context/verification/evidence/ownership/decision all `true`; capability gap none.
- Build scores: scope_coupling=2, state_concurrency=0, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=2 -> G06.
- Build route: base=`local-fit`, route=`local-fit`, lane=`local`, filename=`PLAN-local-G06.md`.
- large_indivisible_context: `false`.
- matched loop risk: `boundary_contract`; count=1; risk boundary=false.
- recovery signals: review_rework_count=0, evidence_integrity_failure=false; recovery boundary=false.
- Review closures: scope/context/verification/evidence/ownership/decision all `true`; capability gap none.
- Review scores: scope_coupling=2, state_concurrency=0, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=2 -> G06.
- Review route: `official-review`, lane=`cloud`, adapter=`codex`, model=`gpt-5.6-sol`, reasoning=`xhigh`, filename=`CODE_REVIEW-cloud-G06.md`.

## Implementation Checklist

- [ ] [API-1] Register distinct `glm` General API and `glm_coding` Coding Plan profiles, including tool-calling capability, and add literal config plus Node transport regressions.
- [ ] [API-2] Permit one `glm/bearer` managed credential slot to bind independently to both GLM profiles and test exact route alias/resource-selector isolation.
- [ ] [API-3] Document the model-driven two-provider Edge configuration and synchronize the current provider-pool spec without enabling Coding Plan or adding fallback.
- [ ] [API-4] Extend the local Edge smoke profile with the existing SOPS-backed Coding Plan checks and run fresh focused, build, repository E2E, and redacted upstream verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Add the Coding Plan protocol profile and transport regression

#### Problem

`packages/go/config/protocol_profile.go:186-195` defines only the General API profile and omits `tool_calling`. `packages/go/config/protocol_profile_test.go:301-420` and `:689-717` therefore cannot detect a missing Coding Plan identity, wrong URL/auth, accidental Responses support, or catalog count drift. `apps/node/internal/adapters/openai_compat/protocol_profile_test.go:19-44` exercises only `/api/paas/v4/chat/completions`.

Current:

```go
// packages/go/config/protocol_profile.go:186
"glm": {
    Driver:  ProtocolDriverOpenAIChat,
    BaseURL: "https://api.z.ai/api/paas/v4",
    // ...
    Capabilities: []string{"models", "chat", "streaming"},
},
```

#### Solution

Keep `glm` as General API, add its official `tool_calling` capability, and register a separate built-in `glm_coding` with the exact Coding Plan base URL. Both profiles expose only `models` and `chat_completions` operations with Bearer auth; neither exposes Responses.

Target:

```go
"glm": {
    Driver:  ProtocolDriverOpenAIChat,
    BaseURL: "https://api.z.ai/api/paas/v4",
    Operations: map[string]string{
        string(OperationModels):          "/models",
        string(OperationChatCompletions): "/chat/completions",
    },
    Auth:         ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
    Capabilities: []string{"models", "chat", "streaming", "tool_calling"},
},
"glm_coding": {
    Driver:  ProtocolDriverOpenAIChat,
    BaseURL: "https://api.z.ai/api/coding/paas/v4",
    Operations: map[string]string{
        string(OperationModels):          "/models",
        string(OperationChatCompletions): "/chat/completions",
    },
    Auth:         ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
    Capabilities: []string{"models", "chat", "streaming", "tool_calling"},
},
```

Add literal tests that:

- include `glm_coding` in the exact built-in ID/auth matrix;
- resolve General and Coding URLs to `/api/paas/v4/...` and `/api/coding/paas/v4/...` respectively;
- assert both profiles have models/chat/streaming/tool_calling and lack Responses operation/capability;
- load a provider-pool YAML with external `glm-5.1-api` -> provider `glm-api`/profile `glm` and `glm-5.1-coding` -> provider `glm-coding`/profile `glm_coding`, both rewriting to upstream `glm-5.1`, and assert no cross-mapping;
- add `glm_coding` to the Node loopback operation fixture so JSON, SSE, and provider-error responses all hit the Coding Plan path with Bearer auth.

#### Modified Files and Checklist

- [ ] `packages/go/config/protocol_profile.go` — add `glm_coding` and `tool_calling` for both GLM profiles.
- [ ] `packages/go/config/protocol_profile_test.go` — update exact catalog/auth matrices and add URL/capability/provider-pool isolation regressions.
- [ ] `apps/node/internal/adapters/openai_compat/protocol_profile_test.go` — add the Coding Plan operation fixture and update the official-doc verification comment/date.

#### Test Strategy

Write tests. Extend `TestBuiltInProtocolProfileCatalog`, `TestBuiltInProtocolProfileURLs`, and `TestProtocolProfileBuiltInAuthMatrix`; add `TestBuiltInGLMProtocolProfiles` and `TestGLMProfilesRemainDistinctThroughProviderPoolConfig`; add `glm_coding` to `TestProtocolProfileOperationURLFixtures`. Use only literal expected values, temporary YAML, and `httptest.Server`—never production catalog values as expected fixtures or a live key.

#### Verification

Run:

```bash
gofmt -w packages/go/config/protocol_profile.go packages/go/config/protocol_profile_test.go apps/node/internal/adapters/openai_compat/protocol_profile_test.go
go test -count=1 ./packages/go/config ./apps/node/internal/adapters/openai_compat
```

Expected: both packages pass fresh; the loopback fixture records `/api/coding/paas/v4/chat/completions` and no live provider is called.

### [API-2] Bind the same GLM slot to isolated General and Coding routes

#### Problem

`apps/control-plane/internal/credentialstore/route.go:82-95` intentionally uses a closed compatibility matrix, but `glm/bearer` admits only `glm`. This prevents a managed GLM credential slot from selecting `glm_coding` and leaves no regression for same vendor/model with different endpoint profiles.

Current:

```go
// apps/control-plane/internal/credentialstore/route.go:91
"glm/bearer": {"glm": {header: "Authorization", scheme: "Bearer"}},
```

#### Solution

Add `glm_coding` to the existing `glm/bearer` profile rule with the same exact Bearer declaration. Do not introduce a subscription credential kind: subscription/pay-go is a protocol-profile endpoint distinction, while the credential remains vendor `glm` and kind `bearer`.

Target:

```go
"glm/bearer": {
    "glm":        {header: "Authorization", scheme: "Bearer"},
    "glm_coding": {header: "Authorization", scheme: "Bearer"},
},
```

Add `TestGLMSlotSupportsGeneralAndCodingProfiles` beside the multi-profile MiniMax test at `apps/control-plane/internal/credentialstore/route_test.go:222-240`. Create one draft GLM Bearer slot, then create:

- alias `glm-5.1-api`, profile `glm`, upstream `glm-5.1`, selector `glm-api`;
- alias `glm-5.1-coding`, profile `glm_coding`, upstream `glm-5.1`, selector `glm-coding`.

Assert both routes share the slot but preserve distinct profile/alias/selector values, listing returns both, and an OpenAI Bearer slot rejects `glm_coding` with `ErrIncompatibleProfile`.

#### Modified Files and Checklist

- [ ] `apps/control-plane/internal/credentialstore/route.go` — extend only the `glm/bearer` closed rule.
- [ ] `apps/control-plane/internal/credentialstore/route_test.go` — add same-slot dual-profile and cross-vendor rejection coverage.

#### Test Strategy

Write `TestGLMSlotSupportsGeneralAndCodingProfiles` using the existing SQLite temp store, fake key registry, opaque envelope, and real catalog resolution. It must assert the stored route fields rather than only checking a nil error.

#### Verification

Run:

```bash
gofmt -w apps/control-plane/internal/credentialstore/route.go apps/control-plane/internal/credentialstore/route_test.go
go test -count=1 ./apps/control-plane/internal/credentialstore
```

Expected: the GLM dual-profile route test passes and the existing incompatible-profile tests remain green.

### [API-3] Show model-driven Edge selection and synchronize the current spec

#### Problem

`configs/edge.yaml:200-240` lists only `glm` and provides no safe example for choosing the endpoint through an external model ID. `agent-spec/runtime/provider-pool-config-refresh.md:91-116` describes generic model/profile routing but not the two stable GLM profiles or their no-fallback invariant.

Current:

```yaml
#   glm             — openai_chat driver, https://api.z.ai/api/paas/v4
```

#### Solution

Add `glm_coding` to the built-in list and a comment-only provider-pool example. External model IDs—not an extra request field—select separate provider IDs:

```yaml
# models:
#   - id: "glm-5.1-api"
#     providers:
#       glm-api: "glm-5.1"
#   - id: "glm-5.1-coding"
#     providers:
#       glm-coding: "glm-5.1"
# nodes:
#   - id: "node-glm-example"
#     providers:
#       - id: "glm-api"
#         type: "openai_api"
#         category: "api"
#         profile: "glm"
#         models: ["glm-5.1"]
#         capacity: 1
#       - id: "glm-coding"
#         type: "openai_api"
#         category: "api"
#         profile: "glm_coding"
#         models: ["glm-5.1"]
#         capacity: 1
```

State beside the example that both provider IDs must never be placed under the same external model ID when endpoint/quota isolation is required, no General/Coding fallback exists, no raw key belongs in YAML, and Coding Plan may be enabled only for use allowed by current Z.AI terms. Update the current runtime spec feature/config/limitation/change-record sections with the same implemented facts; do not copy credential values or create a new contract.

#### Modified Files and Checklist

- [ ] `configs/edge.yaml` — document the built-in profile and the comment-only two-model/two-provider mapping with usage/fallback guardrails.
- [ ] `agent-spec/runtime/provider-pool-config-refresh.md` — synchronize the implemented GLM profile/routing capability, no-fallback boundary, validation references, and 2026-08-02 change record.

#### Test Strategy

No separate prose-only test. API-1's `TestGLMProfilesRemainDistinctThroughProviderPoolConfig` loads the same mapping shape and is the executable contract. Review the tracked diff for raw-key patterns and ensure the example stays fully commented.

#### Verification

Run:

```bash
rg --sort path -n 'glm_coding|glm-5\.1-api|glm-5\.1-coding|glm-api|glm-coding' configs/edge.yaml agent-spec/runtime/provider-pool-config-refresh.md
git diff --check -- configs/edge.yaml agent-spec/runtime/provider-pool-config-refresh.md
```

Expected: both IDs/profiles appear with distinct provider mappings, and no whitespace error or secret value is present.

### [API-4] Record and execute the SOPS-backed Coding Plan smoke

#### Problem

`agent-test/local/edge-smoke.md:25-48` only says to use a provider-specific command. It does not record the existing encrypted token location, permission/encryption preflight, exact Coding Plan URL, chat/stream/tool acceptance checks, or the prohibition on printing credentials and silently switching to General API.

#### Solution

Update the existing local profile (it is intentionally gitignored/operator-local) and `last_rule_updated_at`. Add a `GLM Coding Plan` subsection containing:

- opt-in/terms precondition and a ban on shared/proxy production use without provider authorization;
- exact SOPS/age paths, `0600` checks, `sops filestatus` encryption assertion, and non-empty scalar extraction without echo;
- a mode-`0600` temporary curl config carrying the Bearer header so the raw key is absent from command arguments/output;
- `GET /api/coding/paas/v4/models` with a `glm-5.1` assertion;
- non-stream Chat with model/content/usage assertions;
- SSE Chat with at least one `data:` event and terminal `data: [DONE]`;
- an auto function-calling request whose response contains the declared function name;
- cleanup and an explicit statement that this procedure never calls `/api/paas/v4` as fallback.

Keep all response artifacts in a `mktemp -d` directory outside the repository and print only a final PASS marker. Then run the repository commands and live smoke exactly as recorded.

#### Modified Files and Checklist

- [ ] `agent-test/local/edge-smoke.md` — add the redacted SOPS-backed Coding Plan preflight, calls, acceptance criteria, cleanup, terms guard, and date.

#### Test Strategy

Update the existing test profile; do not create a new profile or tracked secret fixture. The live check uses the user's host-local encrypted token, while repository behavior remains covered by API-1/API-2 loopback/config tests. If provider terms or token eligibility block the live call, record the exact blocker in review evidence and do not try the General API endpoint.

#### Verification

Run the exact redacted command in Final Verification step 4 and copy the same command/criteria into the `GLM Coding Plan` section of `agent-test/local/edge-smoke.md`.

Expected: preflight passes without revealing the token; models, non-stream, stream, and function-call assertions pass against only `https://api.z.ai/api/coding/paas/v4`; the command prints `glm coding smoke: PASS` and removes temporary files.

## Modified Files Summary

| File | Item |
|------|------|
| `packages/go/config/protocol_profile.go` | API-1 |
| `packages/go/config/protocol_profile_test.go` | API-1 |
| `apps/node/internal/adapters/openai_compat/protocol_profile_test.go` | API-1 |
| `apps/control-plane/internal/credentialstore/route.go` | API-2 |
| `apps/control-plane/internal/credentialstore/route_test.go` | API-2 |
| `configs/edge.yaml` | API-3 |
| `agent-spec/runtime/provider-pool-config-refresh.md` | API-3 |
| `agent-test/local/edge-smoke.md` | API-4 |
| `agent-task/glm_coding_plan/CODE_REVIEW-cloud-G06.md` | API-1, API-2, API-3, API-4 |

## Final Verification

1. Confirm the checkout and external secret preflight. These commands must not print decrypted data:

```bash
test "$(git branch --show-current)" = "feature/glm-coding-plan"
test "$(git merge-base HEAD origin/dev)" = "32c0754f91b05ee95ab25b1062016d44fba18bf2"
test "$(stat -c '%a' /config/.config/iop/secrets/dev-openai-toki.sops.yaml)" = "600"
test "$(stat -c '%a' /config/.config/sops/age/keys.txt)" = "600"
SOPS_AGE_KEY_FILE=/config/.config/sops/age/keys.txt sops filestatus /config/.config/iop/secrets/dev-openai-toki.sops.yaml | jq -e '.encrypted == true'
test "$(SOPS_AGE_KEY_FILE=/config/.config/sops/age/keys.txt sops decrypt --extract '["tokens"]["glm-coding-plan"]' /config/.config/iop/secrets/dev-openai-toki.sops.yaml | wc -c)" -gt 1
```

Expected: every command exits 0 and only the non-secret filestatus JSON is emitted.

2. Confirm formatting and run fresh focused tests:

```bash
test -z "$(gofmt -d packages/go/config/protocol_profile.go packages/go/config/protocol_profile_test.go apps/control-plane/internal/credentialstore/route.go apps/control-plane/internal/credentialstore/route_test.go apps/node/internal/adapters/openai_compat/protocol_profile_test.go)"
go test -count=1 ./packages/go/config ./apps/control-plane/internal/credentialstore ./apps/node/internal/adapters/openai_compat
```

Expected: no gofmt diff and all three packages pass.

3. Run the Edge/config user-path build and repository-native full cycle:

```bash
make build-edge
make test-e2e
```

Expected: Edge builds and the repository Edge-Node E2E exits 0. Record actual stdout/stderr; this verifies the existing user pipeline but does not claim a live Coding Plan proxy deployment.

4. Execute the redacted Coding Plan smoke. This command uses only the Coding endpoint and prints no response body or credential:

```bash
bash <<'BASH'
set -euo pipefail
set +x

sops_file=/config/.config/iop/secrets/dev-openai-toki.sops.yaml
age_file=/config/.config/sops/age/keys.txt
base_url=https://api.z.ai/api/coding/paas/v4
smoke_dir="$(mktemp -d /tmp/iop-glm-coding-smoke.XXXXXX)"
cleanup() {
  rm -rf -- "$smoke_dir"
}
trap cleanup EXIT

test "$(stat -c '%a' "$sops_file")" = "600"
test "$(stat -c '%a' "$age_file")" = "600"
SOPS_AGE_KEY_FILE="$age_file" sops filestatus "$sops_file" | jq -e '.encrypted == true' >/dev/null
token="$(SOPS_AGE_KEY_FILE="$age_file" sops decrypt --extract '["tokens"]["glm-coding-plan"]' "$sops_file")"
test -n "$token"

umask 077
auth_config="$smoke_dir/curl-auth.conf"
printf 'header = "Authorization: Bearer %s"\nheader = "Content-Type: application/json"\n' "$token" >"$auth_config"
unset token

curl --fail --silent --show-error --config "$auth_config" \
  --output "$smoke_dir/models.json" "$base_url/models"
jq -e '(.data | type == "array") and any(.data[]; (.id | ascii_downcase) == "glm-5.1")' \
  "$smoke_dir/models.json" >/dev/null

jq -n '{
  model: "glm-5.1",
  messages: [{role: "user", content: "Reply exactly GLM_CODING_PLAN_OK"}],
  stream: false,
  max_tokens: 32
}' >"$smoke_dir/chat.json"
curl --fail --silent --show-error --config "$auth_config" \
  --request POST --data-binary "@$smoke_dir/chat.json" \
  --output "$smoke_dir/chat-response.json" "$base_url/chat/completions"
jq -e '(.choices[0].message.content | type == "string" and length > 0) and (.usage.total_tokens | type == "number")' \
  "$smoke_dir/chat-response.json" >/dev/null

jq -n '{
  model: "glm-5.1",
  messages: [{role: "user", content: "Reply exactly GLM_CODING_PLAN_STREAM_OK"}],
  stream: true,
  max_tokens: 32
}' >"$smoke_dir/stream.json"
curl --fail --silent --show-error --no-buffer --config "$auth_config" \
  --request POST --data-binary "@$smoke_dir/stream.json" \
  --output "$smoke_dir/stream-response.txt" "$base_url/chat/completions"
grep -Eq '^data: .+' "$smoke_dir/stream-response.txt"
grep -Fq 'data: [DONE]' "$smoke_dir/stream-response.txt"

jq -n '{
  model: "glm-5.1",
  messages: [{role: "user", content: "Call emit_marker with marker GLM_CODING_PLAN_TOOL_OK. Do not answer directly."}],
  tools: [{
    type: "function",
    function: {
      name: "emit_marker",
      description: "Emit the requested verification marker",
      parameters: {
        type: "object",
        properties: {marker: {type: "string"}},
        required: ["marker"]
      }
    }
  }],
  tool_choice: "auto",
  stream: false,
  max_tokens: 64
}' >"$smoke_dir/tool.json"
curl --fail --silent --show-error --config "$auth_config" \
  --request POST --data-binary "@$smoke_dir/tool.json" \
  --output "$smoke_dir/tool-response.json" "$base_url/chat/completions"
jq -e 'any(.choices[0].message.tool_calls[]?; .function.name == "emit_marker")' \
  "$smoke_dir/tool-response.json" >/dev/null

printf 'glm coding smoke: PASS\n'
BASH
```

Expected: `/models`, non-stream Chat, SSE `[DONE]`, and one tool call pass at `https://api.z.ai/api/coding/paas/v4` only; the command prints `glm coding smoke: PASS` and removes all temporary files.

5. Inspect scope and secret safety:

```bash
git diff --check
git diff --name-only -- packages/go/config/protocol_profile.go packages/go/config/protocol_profile_test.go apps/control-plane/internal/credentialstore/route.go apps/control-plane/internal/credentialstore/route_test.go apps/node/internal/adapters/openai_compat/protocol_profile_test.go configs/edge.yaml agent-spec/runtime/provider-pool-config-refresh.md
rg --sort path -n 'glm_coding|glm-5\.1-api|glm-5\.1-coding' packages/go/config/protocol_profile.go packages/go/config/protocol_profile_test.go apps/control-plane/internal/credentialstore/route.go apps/control-plane/internal/credentialstore/route_test.go apps/node/internal/adapters/openai_compat/protocol_profile_test.go configs/edge.yaml agent-spec/runtime/provider-pool-config-refresh.md agent-test/local/edge-smoke.md
```

Expected: only the claimed implementation files plus active review evidence are modified; GLM split identifiers are present; no token/API key value appears in the diff or command output.

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