From 9b2fc2ae473993bc2d9f308658438ff45018f5b6 Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 2 Aug 2026 12:08:13 +0900 Subject: [PATCH] =?UTF-8?q?docs(agent-task):=20GLM=20Coding=20Plan=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EA=B3=84=ED=9A=8D=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 일반 API와 구독 endpoint가 동일 모델명으로 섞이지 않도록 profile, credential route, Edge 모델 매핑 및 검증 경계를 구현 전에 확정한다. --- .../glm_coding_plan/CODE_REVIEW-cloud-G06.md | 298 ++++++++++ agent-task/glm_coding_plan/PLAN-local-G06.md | 513 ++++++++++++++++++ 2 files changed, 811 insertions(+) create mode 100644 agent-task/glm_coding_plan/CODE_REVIEW-cloud-G06.md create mode 100644 agent-task/glm_coding_plan/PLAN-local-G06.md diff --git a/agent-task/glm_coding_plan/CODE_REVIEW-cloud-G06.md b/agent-task/glm_coding_plan/CODE_REVIEW-cloud-G06.md new file mode 100644 index 00000000..12562af9 --- /dev/null +++ b/agent-task/glm_coding_plan/CODE_REVIEW-cloud-G06.md @@ -0,0 +1,298 @@ + + +# Code Review Reference - API + +> **[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 `Implementation Checklist`; 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 (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## Overview + +date=2026-08-02 +task=glm_coding_plan, plan=0, tag=API + +## For the Review Agent + +> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section. + +Compare implementation of each item against source files and verify that output in `Verification Results` matches code. +Review completion means the following steps are finished: + +1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals. +2. Archive `CODE_REVIEW-cloud-G06.md` → `code_review_cloud_G06_0.log` and `PLAN-local-G06.md` → `plan_local_G06_0.log`. +3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/glm_coding_plan/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill. +4. If PASS and task group is `m-`, preserve the first-line `milestone-task` metadata in `complete.log` and report it for the runtime aggregation event. Roadmap state evaluation belongs to `sync-milestone-workstate`. +5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting. + +--- + +## Implementation Item Completion + +| Item | Status | +|------|---------| +| API-1 - GLM General/Coding profile catalog and transport regression | [ ] | +| API-2 - Managed GLM credential dual-profile route isolation | [ ] | +| API-3 - Edge model-driven configuration and current spec | [ ] | +| API-4 - SOPS-backed Coding Plan smoke and verification | [ ] | + +## 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. + +## Review-Only Checklist + +> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent. +> Implementing agents must not modify or check this section. + +- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`. +- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match. +- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G06_0.log`. +- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G06_0.log`. +- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`. +- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files. +- [ ] If PASS, move active task directory `agent-task/glm_coding_plan/` to `agent-task/archive/YYYY/MM/glm_coding_plan/` and update this checklist at the final archive path. +- [ ] If PASS and task group is `m-`, preserve and report `milestone-task` metadata for runtime aggregation, without modifying roadmap or directly calling `update-roadmap`. +- [ ] If PASS for split work, remove empty active parent `agent-task/glm_coding_plan/` or verify it was kept due to remaining siblings/files. +- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`. + +## Deviations from Plan + +_Record any deviations from the plan and the rationale here._ + +## Key Design Decisions + +_Record key design decisions here._ + +## Reviewer Checkpoints + +- Verify `glm` remains bound to `https://api.z.ai/api/paas/v4` and `glm_coding` to `https://api.z.ai/api/coding/paas/v4`, with Bearer auth, models/chat/streaming/tool_calling, and no Responses operation/capability. +- Verify the literal catalog/auth count tests and Node loopback fixture include `glm_coding` and do not derive expected values from production catalog data. +- Verify one `glm/bearer` slot can store distinct `glm`/`glm_coding` routes with aliases `glm-5.1-api`/`glm-5.1-coding` and selectors `glm-api`/`glm-coding`, while another vendor is rejected. +- Verify the Edge example never maps both providers under one external model ID, never enables a provider by default, never adds fallback, and records the Z.AI supported-use/proxy restriction without a credential. +- Verify no config schema, proto, driver, Edge handler, contract, roadmap, SOPS file, or token source was changed outside the plan. +- Verify the local smoke uses the SOPS scalar only through a mode-0600 temporary curl config, calls the Coding base URL only, removes temporary files, and records actual models/chat/SSE/tool evidence without response or credential leakage. +- Verify focused tests use `-count=1`, `make build-edge` and `make test-e2e` actually ran, and every command's stdout/stderr is pasted below. + +## Verification Results + +> **[IMPLEMENTING AGENT]** Run every command exactly as written. Paste actual stdout/stderr under the matching `Actual Output` heading. If a command changes, record the replacement and reason in `Deviations from Plan` before pasting its output. + +### API-1 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. + +#### Actual Output + +_Paste actual stdout/stderr here._ + +### API-2 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. + +#### Actual Output + +_Paste actual stdout/stderr here._ + +### API-3 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. + +#### Actual Output + +_Paste actual stdout/stderr here._ + +### API-4 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. + +#### Actual Output + +_Paste actual stdout/stderr here._ + +### 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`. + +#### Actual Output + +_Paste actual stdout/stderr for each numbered command here._ + +--- + +> **[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 Ownership + +| Section | Owner | Note | +|---------|-------|------| +| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, `complete.log`, and task-directory archive move are review-agent only) | +| 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 | +| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only | +| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only | +| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section | +| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content | +| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan | +| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry | +| Code Review Result | Review agent appends | Not included in stub | diff --git a/agent-task/glm_coding_plan/PLAN-local-G06.md b/agent-task/glm_coding_plan/PLAN-local-G06.md new file mode 100644 index 00000000..f6b18fdf --- /dev/null +++ b/agent-task/glm_coding_plan/PLAN-local-G06.md @@ -0,0 +1,513 @@ + + +# 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`.