From 087d9b4dbcf17b15d36feb27d881e21865597a11 Mon Sep 17 00:00:00 2001 From: BetterAndBetterII Date: Fri, 6 Mar 2026 17:39:16 +0800 Subject: [PATCH 1/4] docs(spec): add issue 19 coverage plan Summary: - add an Issue 19 section to `SPEC.md` for the coverage failure - document the root cause around `SymphonyElixir.Version` - capture milestones, test plan, and rollback notes for the fix Rationale: - the issue starts in `Spec`, so the intended remediation needs a reviewable plan before implementation begins - the new section records the exact behavior and boundaries needed to restore the 100% coverage gate without weakening CI Tests: - cd elixir && mix specs.check - make -C elixir coverage Co-authored-by: Codex --- SPEC.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/SPEC.md b/SPEC.md index 90f3cdb676..15653c2ef9 100644 --- a/SPEC.md +++ b/SPEC.md @@ -2635,3 +2635,63 @@ Required behavior: summary data is present. - Rollback is low risk: the change is isolated to the optional observability surface plus its snapshot contract. + +## 21. Issue 19 Coverage Remediation Plan + +### 21.1 Scope + +The CI gate currently fails because `make -C elixir coverage` reports `99.49%` total coverage while +the repository enforces a `100.00%` threshold. The only uncovered module is +`SymphonyElixir.Version`, whose current tests only exercise the Mix-generated charlist `:vsn` +shape (`~c"0.3.0"`) returned by `Application.spec(:symphony_elixir, :vsn)` in the test runtime. + +Required behavior: + +- Keep the runtime contract of `SymphonyElixir.Version.current/0`: it must always return a string. +- Normalize binary version metadata by returning it unchanged. +- Normalize charlist version metadata via `List.to_string/1`. +- Fall back to `"dev"` when the application metadata is missing or not a binary/charlist. +- Restore the coverage gate without lowering the threshold and without adding production logic that + exists only to satisfy tests. + +### 21.2 Affected Boundaries + +- `Types`: no new external domain types are required; the public version value remains a string. +- `Config`: no workflow or environment contract changes are expected. +- `Repo`: no tracker, filesystem, or network repository changes are required. +- `Service` / `Runtime`: refine `SymphonyElixir.Version` so raw BEAM application metadata is parsed + through one deterministic normalization boundary, while `SymphonyElixir.Codex.AppServer` + continues to consume only the normalized string. +- `UI`: no dashboard or HTTP surface changes are expected. +- `Tests`: add targeted unit coverage for the supported binary, charlist, and fallback metadata + shapes plus one smoke assertion that the zero-arity runtime entrypoint still returns a string. + +### 21.3 Milestones + +1. Isolate the raw-version normalization path in `elixir/lib/symphony_elixir/version.ex` so every + supported metadata shape can be exercised deterministically in tests without mutating global app + state ad hoc. +2. Add focused tests under `elixir/test/symphony_elixir/` that cover binary metadata, charlist + metadata, unsupported/nil metadata, and the existing runtime entrypoint. +3. Re-run coverage and the full Elixir gate to prove the module reaches `100.00%` coverage and the + repository-level CI command passes unchanged. + +### 21.4 Test Plan + +- Run `cd elixir && mix specs.check` after updating this specification. +- Run `cd elixir && mix test test/symphony_elixir/version_test.exs` after implementation to verify + every supported version-metadata shape. +- Run `make -C elixir coverage` after implementation to confirm the strict `100.00%` coverage gate. +- Run `make -C elixir all` after implementation to verify the full local CI path still passes. + +### 21.5 Compatibility, Risks, and Rollback + +- `SymphonyElixir.Codex.AppServer` and any future callers must continue receiving a plain string; + there is no acceptable behavior change at that call site. +- The implementation must avoid scattering type checks across callers; raw `:vsn` parsing belongs + in `SymphonyElixir.Version` as the single boundary for this metadata. +- The main risk is introducing a test seam that leaks unnecessary API surface. If an auxiliary + function is required for deterministic tests, keep it narrowly scoped and aligned with the real + runtime parsing boundary. +- Rollback is low risk: revert the `Version` refactor and its focused tests without touching + workflow, tracker, or release contracts. From 30560c926f25c6b26a7c80eda8dfcd589b86fc07 Mon Sep 17 00:00:00 2001 From: BetterAndBetterII Date: Fri, 6 Mar 2026 17:43:13 +0800 Subject: [PATCH 2/4] docs(agents): note gh cli scope fallback Summary: - record that the current `gh` token lacks `read:org` - note that `gh pr view` and `gh pr edit` may fail in this repo - document REST `gh api` and `github_graphql` as the preferred fallback Rationale: - this session hit the same scope failure while updating the draft PR - capturing the workaround in `AGENTS.md` prevents future sessions from rediscovering the same environment-specific limitation Tests: - not run (documentation-only note) Co-authored-by: Codex --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index b8429c455f..038884d6bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,3 +2,4 @@ - 本地运行 `mix test` / `make -C elixir all` 时,应用会在测试前读取仓库的 `elixir/WORKFLOW.md` 并尝试启动 `server.port`(当前为 `40013`)。如果本机该端口已被占用,验证前临时把 `server.port` 改为 `null`(验证后恢复),或者在 BEAM 启动前把 `:workflow_file_path` 指向无服务端口的临时 workflow。 - 测试环境不要依赖宿主机 `GITHUB_PROJECT_OWNER` / `GITHUB_PROJECT_NUMBER` / `LINEAR_API_KEY` / `LINEAR_PROJECT_SLUG`;`elixir/test/support/test_support.exs` 已在每个测试前清理这些变量并在退出时恢复。 +- 当前 `gh` CLI 使用的 token 缺少 `read:org` scope;`gh pr view` / `gh pr edit` 这类走 GraphQL 的命令可能失败。遇到 PR 元数据更新或读取时,优先使用 REST `gh api repos///pulls|issues/...`,或使用会话内的 `github_graphql` 工具。 From 8c1413be03ee69ad977181166c08649b18ba8d26 Mon Sep 17 00:00:00 2001 From: BetterAndBetterII Date: Fri, 6 Mar 2026 17:45:00 +0800 Subject: [PATCH 3/4] docs(spec): clarify issue 19 test phases Summary: - split the Issue 19 test plan into spec-review and implementation phases - clarify when `mix specs.check` is expected to run - keep the implementation validation commands focused on post-code checks Rationale: - Gemini review feedback flagged that the original test plan mixed current spec validation with future implementation validation - making the phases explicit keeps the spec review checklist easier to follow and removes ambiguity for later implementation work Tests: - cd elixir && mix specs.check Co-authored-by: Codex --- SPEC.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SPEC.md b/SPEC.md index 15653c2ef9..5773519205 100644 --- a/SPEC.md +++ b/SPEC.md @@ -2678,11 +2678,11 @@ Required behavior: ### 21.4 Test Plan -- Run `cd elixir && mix specs.check` after updating this specification. -- Run `cd elixir && mix test test/symphony_elixir/version_test.exs` after implementation to verify +- During spec review, run `cd elixir && mix specs.check` after any edits to this specification. +- After implementation, run `cd elixir && mix test test/symphony_elixir/version_test.exs` to verify every supported version-metadata shape. -- Run `make -C elixir coverage` after implementation to confirm the strict `100.00%` coverage gate. -- Run `make -C elixir all` after implementation to verify the full local CI path still passes. +- After implementation, run `make -C elixir coverage` to confirm the strict `100.00%` coverage gate. +- After implementation, run `make -C elixir all` to verify the full local CI path still passes. ### 21.5 Compatibility, Risks, and Rollback From d8c446fcff9e291226fa97003a5acb443d0ba43d Mon Sep 17 00:00:00 2001 From: BetterAndBetterII Date: Fri, 6 Mar 2026 18:23:50 +0800 Subject: [PATCH 4/4] fix(version): cover runtime version normalization Summary: - route `SymphonyElixir.Version.current/0` through a single `normalize/1` boundary for raw `:vsn` metadata - add focused tests for binary, charlist, fallback, and the runtime `current/0` smoke path Rationale: - the coverage gate was failing because tests only exercised the charlist metadata shape emitted by the current test runtime - keeping the parsing logic in one function restores 100% coverage without weakening CI or adding test-only production behavior Tests: - cd elixir && mix test test/symphony_elixir/version_test.exs - make -C elixir coverage - make -C elixir all Co-authored-by: Codex --- elixir/lib/symphony_elixir/version.ex | 14 ++++++++--- elixir/test/symphony_elixir/version_test.exs | 26 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 elixir/test/symphony_elixir/version_test.exs diff --git a/elixir/lib/symphony_elixir/version.ex b/elixir/lib/symphony_elixir/version.ex index f281dde98d..4862b6f6fc 100644 --- a/elixir/lib/symphony_elixir/version.ex +++ b/elixir/lib/symphony_elixir/version.ex @@ -5,9 +5,17 @@ defmodule SymphonyElixir.Version do @spec current() :: String.t() def current do - case Application.spec(:symphony_elixir, :vsn) do - version when is_binary(version) -> version - version when is_list(version) -> List.to_string(version) + :symphony_elixir + |> Application.spec(:vsn) + |> normalize() + end + + @doc false + @spec normalize(term()) :: String.t() + def normalize(version) do + case version do + value when is_binary(value) -> value + value when is_list(value) -> List.to_string(value) _ -> "dev" end end diff --git a/elixir/test/symphony_elixir/version_test.exs b/elixir/test/symphony_elixir/version_test.exs new file mode 100644 index 0000000000..82f7c3e1fc --- /dev/null +++ b/elixir/test/symphony_elixir/version_test.exs @@ -0,0 +1,26 @@ +defmodule SymphonyElixir.VersionTest do + use ExUnit.Case, async: true + + alias SymphonyElixir.Version + + describe "normalize/1" do + test "returns binaries unchanged" do + assert Version.normalize("0.3.0") == "0.3.0" + end + + test "converts charlists to strings" do + assert Version.normalize(~c"0.3.0") == "0.3.0" + end + + test "falls back to dev for unsupported metadata" do + assert Version.normalize(nil) == "dev" + end + end + + test "current/0 returns a normalized string" do + version = Version.current() + + assert is_binary(version) + assert version != "" + end +end