# 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`.