myxce/.kiro/specs/eliminate-frontend-makefile/design.md
Rongsong Shen be9a03a3e9
docs(eliminate-frontend-makefile): add approved spec (requirements/design/tasks)
Requirements: 6 requirement areas covering single build path, unit/
backend/integration test migration, binary discovery, and doc
consistency. Design: 6 rounds of independent adversarial review to
convergence (2 real technical bugs caught -- unresolved Meson
placeholder tokens in CGO flags, invalid unquoted dict-literal keys --
plus a long tail of stale Makefile references across docs/scripts/
source comments, closed by a scope-rule restructure rather than
continued line-by-line patching). Tasks: 4 major / 12 sub-tasks,
task-graph sanity review passed after one repair round.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
2026-09-03 13:03:00 +00:00

232 lines
31 KiB
Markdown

# Design Document
## Overview
**Purpose**: Delete `src/frontend/Makefile` and consolidate apiserver/xcecli build and test execution onto Meson, eliminating a real, already-realized duplicate version-fingerprint-computation risk.
**Users**: XCE developers building/testing apiserver and xcecli, and the project's own canonical build tooling (`xce-build.sh`, `/xce-build`) and dependent scripts (`run-resource-verify.sh`).
**Impact**: `src/frontend/Makefile` (194 lines, 5 targets) is removed entirely. Its `build-cli`/`build-server` targets are already dead from `/xce-build`'s perspective (Meson already builds both binaries). Its `test` target's two live callers migrate to direct `go test` invocations. Its `test-backend` target's coverage moves into a genuine Meson `test()` registration. Its `test-integration` target's one line of logic (an env-var rename) is absorbed by direct invocation of the already-standalone `tests/regression/run.sh`.
### Goals
- Exactly one build/test orchestration path for apiserver/xcecli exists after this lands.
- No CGO-backed backend test coverage is lost at any point during the migration.
- No script or document is left depending on a Makefile-specific fixed path or target.
### Non-Goals
- Redesigning `apiserver/meson.build`'s existing CGO flag derivation (`apiserver_cgo_deps`) — reused as-is.
- Changing `xce-build.sh`'s version-fingerprint computation — the single surviving source of truth is untouched.
- Rewriting historical/frozen spec documents.
- Fixing `component-failover-adoption`'s unrelated `set -euo pipefail` CGO-test-skip issue.
## Boundary Commitments
### This Spec Owns
- Deletion of `src/frontend/Makefile`.
- The two `xce-build.sh` call sites that invoke `cd frontend && make test` (`apiserver` and `all` cases).
- A new Meson `test()` registration for the CGO-backed backend suite in `apiserver/meson.build`, reusing the existing `apiserver_cgo_deps` derivation.
- `run-resource-verify.sh`'s apiserver-binary discovery (currently the one script in the resource-verify skill still hardcoded to `/tmp/apiserver`; its siblings `BINARY`/`SCHED_BIN`/`XT` already resolve from the Meson build dir).
- The documented invocation convention for `tests/regression/run.sh` (unchanged script; only which binary path callers point `XCE` at).
- `deploy-ha.sh`'s three `make build-server`/`/tmp/apiserver`-referencing message strings (its binary-discovery logic itself is out of boundary — see below).
- Live-documentation updates: `tech.md`, `build-deployment.md`, `testing.md`, `structure.md`, `frontend/CLAUDE.md`, `src/docs/deployment.md`, `xce-deploy-ha/SKILL.md`, `src/frontend/docs/status.md`, `src/docs/apiserver-backend-tasks.md`, `xce-test-resources/SKILL.md`.
- `tests/regression/run.sh`'s own `XCE` binary-not-found error message (currently reproduces the deleted `build-cli` target's exact manual recipe).
- Two C testsuite files' comments (`test_ha_bootstrap.c`, `test_ha_ctrlhello.c`) pointing readers at `make test-integration` by name.
- The root `CLAUDE.md`'s "Key Files" entry naming `src/frontend/Makefile` as "primary build/test orchestration" — the single most-visible live reference in the repo (loaded into every session).
- Both Go modules' `cmd/main.go` ldflags-injection comments (`apiserver/cmd/main.go`, `xcecli/cmd/main.go`) naming the Makefile's `build-server`/`build-cli` targets alongside `src/tools/go_build` as where GitCommit/BuildTime injection happens.
### Out of Boundary
- `apiserver/meson.build`'s CGO flag *values* — only the existing derivation is reused, not redesigned.
- `xce-build.sh`'s version-fingerprint computation (commit id / build time / dirty fingerprint) — unchanged, remains the single source of truth `go_build` already reads via meson options.
- `tools/go_build` itself — unchanged, reused as-is.
- `deploy-ha.sh`'s binary-discovery *logic* (the meson-first / `/tmp/apiserver`-fallback branching itself) — already migrated in a prior session; not re-touched here. Its three `make build-server`/`/tmp/apiserver`-referencing message strings ARE in boundary — see File Structure Plan.
- Historical/frozen spec documents (`tasks.md`/`research.md`/`design.md` of already-completed specs) referencing Makefile targets as acceptance criteria for shipped work.
- `component-failover-adoption`'s `set -euo pipefail` CGO-test-skip gap.
- Test *content* — this is a pure execution-path migration; no test assertions change.
### Allowed Dependencies
- `apiserver/meson.build`'s existing `apiserver_cgo_deps` list and the top-level `src/meson.build` program objects (`go_compiler = find_program('go')`, `pkgconfig`) — read/reused, not modified.
- `xce-build.sh`'s existing `commit_id`/`build_time`/`dirty_fingerprint` meson options — read-only dependency, contract unchanged.
- `run-resource-verify.sh`'s own established `$BUILD_DIR/<component>/<binary>` discovery convention (already used for `BINARY`/`SCHED_BIN`/`XT`) — the apiserver fix follows this existing in-file pattern, not a new one.
### Invariant Cross-Check
This is a pure build-tooling migration — no async I/O, HA/consensus, persistence, or driver/simulation state-equivalence surface is touched. `xce-audit-invariants`' controller/scheduler dimension lists do not apply; no invariant checklist was skipped.
### Revalidation Triggers
- If `apiserver_cgo_deps` (the CGO dependency list in `apiserver/meson.build`) changes, both its consumers — the existing `custom_target` and the new backend `test()` — must be updated together; they now share one derivation, so this should no longer be possible to miss, but any refactor of that block should verify both call sites still compile/run.
- If `xce-build.sh`'s version-fingerprint env var contract (`XCE_GIT_COMMIT`/`XCE_BUILD_TIME`/`XCE_DIRTY_FINGERPRINT`) changes shape, `go_build` and every `custom_target` depending on it need re-verification — pre-existing contract, not introduced here, but worth flagging since Requirement 1 asserts it as the single source of truth.
- Any future script needing to discover a built apiserver/xcecli binary should follow the Meson-build-dir-first pattern established here and in `deploy-ha.sh`, not reintroduce a fixed legacy path.
## Architecture
### Existing Architecture Analysis
Two independent orchestration paths build the same two Go binaries today:
1. **Meson** (`apiserver/meson.build`, `xcecli/meson.build`) — via `tools/go_build`, already the path `/xce-build apiserver`/`/xce-build all` use for the actual binaries.
2. **`src/frontend/Makefile`** — a second, independently-maintained path. Its `build-cli`/`build-server` targets duplicate the version-fingerprint computation `xce-build.sh` already owns; only its `test` target has a live caller (`xce-build.sh`'s `apiserver`/`all` cases, 2 call sites, `cd frontend && make test`). `test-backend`/`test-integration` have no live caller today — they are developer-manual-only, and `build-server` is independently confirmed broken (bug-1597).
The sibling `xcecli` case in `xce-build.sh` already bypasses Make entirely (`cd frontend/xcecli && CGO_ENABLED=0 go test ./... -count=1`) — this is the precedent the apiserver unit-test migration mirrors directly.
### Architecture Pattern & Boundary Map
```mermaid
flowchart TB
subgraph Before[Before]
XB1[xce-build.sh apiserver/all] --> MT1[make test]
MT1 --> UT1[apiserver + xcecli unit tests]
Dev1[Developer, manual] --> MTB[make test-backend]
MTB --> DerivA[Makefile CGO flag derivation]
Dev2[Developer, manual] --> MTI[make test-integration]
MTI --> BC[build-cli]
MTI --> RunSh1[tests/regression/run.sh]
RVS1[run-resource-verify.sh] --> TmpPath[fixed path /tmp/apiserver]
end
subgraph After[After]
XB2[xce-build.sh apiserver/all] --> GT2[direct go test invocations]
GT2 --> UT2[apiserver + xcecli unit tests]
MesonTest[meson test] --> BackendTest[new backend test target]
BackendTest --> DerivB[apiserver_cgo_deps, shared]
Dev3[Developer, manual] --> RunSh2[tests/regression/run.sh]
RunSh2 --> XCEBin[XCE env pointed at meson build dir binary]
RVS2[run-resource-verify.sh] --> BuildDirPath[BUILD_DIR/frontend/apiserver/xce_apiserver]
CustomTarget[apiserver custom_target] --> DerivB
end
```
**Architecture Integration**:
- **Selected pattern**: Consolidation onto the existing Meson build graph; no new orchestration layer introduced.
- **Domain/feature boundaries**: build (Meson `custom_target`, unchanged) stays separate from test execution (Meson `test()` for the backend suite; direct shell/`go test` invocation for unit and integration tests) — matching the existing project convention where C tests are `meson test()` targets but the E2E regression suite (`tests/regression/run.sh`) is not.
- **Existing patterns preserved**: the `xcecli` case's direct-`go test`-invocation style (mirrored for apiserver's unit tests); the Meson `test()` registration pattern already used for controller/scheduler (`build-deployment.md`'s Test Registration example); `run-resource-verify.sh`'s own `$BUILD_DIR/<component>/<binary>` discovery convention (mirrored for the one binary — apiserver — not yet following it).
- **New components rationale**: exactly one new component — the backend-test `test()` registration — since no Meson-testable equivalent exists today for CGO backend coverage. Everything else is migration of an existing call site, not new architecture.
- **Steering compliance**: `build-deployment.md`'s "single hermetic build system" philosophy is the explicit target state this spec completes; no async-I/O, HA, or persistence invariants are touched (pure build-tooling change).
### Technology Stack
| Layer | Choice / Version | Role in Feature | Notes |
|-------|------------------|------------------|-------|
| Build orchestration | Meson ≥ 1.5 (existing) | Sole build/test path for apiserver, xcecli, and the new backend test target | No version change; reuses `go_compiler = find_program('go')` already declared in `src/meson.build` |
| Backend test execution | `go test` invoked directly by Meson `test()` | Runs `./backend/...` under real CGO linkage | No wrapper script — `test()` invokes the `go_compiler` program object directly with `args`/`env`/`workdir` |
| Shell tooling | Bash (existing) | `xce-build.sh` call-site migration, `run-resource-verify.sh` discovery fix | No new script introduced |
## File Structure Plan
### Modified Files
- `src/tools/xce-build.sh` — the `apiserver` case (~line 275) and `all` case (~line 322) replace `cd frontend && make test` with direct invocations: `cd frontend/apiserver && CGO_ENABLED=0 go test ./store/... ./utils/... ./controllers/... ./backend/... -count=1 && cd ../xcecli && CGO_ENABLED=0 go test ./... -count=1` — the exact same package set and flags `make test` runs today, just inlined, mirroring the `xcecli` case's own existing style at line 284.
- `src/frontend/apiserver/meson.build` — no refactor needed to compute the flags: `cgo_cflags`/`cgo_ldflags` are already file-scope Meson variables from the existing `apiserver_cgo_deps` foreach loop (lines 20-38). **They are not directly usable as-is**, though: lines 42-43 append literal `@CURRENT_SOURCE_DIR@`/`@BUILD_ROOT@` placeholder text, which today is resolved only by the existing `custom_target`'s two-step path (meson substitutes those tokens in the `command:` argument list at lines 57-58 into real `CURRENT_SOURCE_DIR`/`BUILD_ROOT` env vars, which `tools/go_build`'s own `fix_env`/`fix_input_envs` shell functions then sed-substitute into `$CGO_CFLAGS`/`$CGO_LDFLAGS` at runtime). The new test bypasses `go_build` entirely (see Non-Goals — `go_build` is hardcoded to a single `go build` invocation, not reusable for `go test`, and is out of boundary to modify), so it must resolve these tokens itself at Meson-configure time: `resolved_cgo_cflags = cgo_cflags.replace('@CURRENT_SOURCE_DIR@', meson.current_source_dir()).replace('@BUILD_ROOT@', meson.project_build_root())` (same pattern for `cgo_ldflags`) — Meson's own `.replace()` string method, evaluated once when `meson.build` is processed, no shell/sed step needed. Add a new `test('apiserver backend', go_compiler, args: ['test', './backend/...', '-count=1'], workdir: meson.current_source_dir(), env: {'CGO_ENABLED': '1', 'CGO_CFLAGS': resolved_cgo_cflags, 'CGO_LDFLAGS': resolved_cgo_ldflags, 'GOFLAGS': '-mod=vendor', 'GOPROXY': 'off', 'GOCACHE': meson.current_build_dir() / 'go-cache', 'GOPATH': meson.current_build_dir() / 'go-path'}, suite: 'apiserver', depends: [libxce])` — dict keys are quoted string literals (Meson dict-literal syntax requires this; an unquoted key parses as a variable reference and fails configure) — `GOFLAGS`/`GOPROXY` mirror `tools/go_build`'s own offline/hermetic conventions (confirmed `apiserver/vendor/` is committed). `GOCACHE`/`GOPATH` are likewise set explicitly (pinned under `meson.current_build_dir()`, not ambient `$TMPDIR`) since, same as the CGO flags, this test gets none of `go_build`'s internal exports for free.
- `.claude/skills/xce-test-resources/run-resource-verify.sh` — line 203: `APISERVER_BIN` changes from `"${XCE_RESVERIFY_APISERVER_BIN:-/tmp/apiserver}"` to `"${XCE_RESVERIFY_APISERVER_BIN:-$BUILD_DIR/frontend/apiserver/xce_apiserver}"`, matching the file's own existing `BINARY`/`SCHED_BIN`/`XT` convention exactly (no dual-path fallback needed — none of its siblings have one, and `/tmp/apiserver` is never populated by anything once the Makefile is gone). Line 350's failure message updates from `"cd src/frontend && make build-server, or the CGO-flags workaround"` to `"run /xce-build apiserver"`, matching the sibling messages' phrasing.
**Scope rule for every file below** (this rule, not any individual line number, is the actual acceptance boundary — three successive review rounds each found a live `make`/fixed-path reference the previous enumeration missed in an already-in-scope file, so the line numbers below are a verified starting inventory, not the boundary itself): update **every** live, current-fact reference to a `src/frontend/Makefile` target (`build-cli`/`build-server`/`test`/`test-backend`/`test-integration`/`test-all`) or a Makefile-specific fixed path (`/tmp/apiserver`, `/tmp/xce_static`) in each named file — including ones in a different section, bullet, or comment block than the ones enumerated here. The Testing Strategy's "Live-reference sweep" is the authoritative completeness check; a task implementing one of these files is not done until that sweep, scoped to that one file, comes back clean.
- `.kiro/steering/tech.md` — the Build Systems table's `Make` row and its `make build-server`/`make build-cli`/`make test`/`make test-integration` code block; **also** lines 81-82 under "Testing Requirements" (`` `make test` must pass without a live server. `` / `` `make test-integration` requires... ``), found only in review round 3 — same file, different section.
- `.kiro/steering/build-deployment.md` — the "Go Frontend Build" section's `make` command block, replaced with the direct invocations this design introduces (unit tests via `xce-build.sh`, backend tests via `meson test --suite apiserver`, integration tests via direct `tests/regression/run.sh` invocation with `XCE`/`URL`).
- `.kiro/steering/testing.md` — the three `make test*` bullet points under "Build targets"; the Philosophy line ("A change is not complete until `make test`... passes"); **also** line 95 under "What Not to Mock" (`` CGO bridge (`backend/`) is covered by `make test-backend`... ``), found only in review round 3.
- `.kiro/steering/structure.md` — the line referencing `make test`/`make test-all`.
- `src/frontend/CLAUDE.md` — the "Build & Test" code block's four `make` commands.
- `src/docs/deployment.md` — the "Build gotcha" section (lines 243-247), already steering developers away from `make build-server`; reword now that the target no longer exists at all (from "avoid this, use X instead" to "this Makefile no longer exists, use X").
- `.claude/skills/xce-deploy-ha/SKILL.md` — references at lines 40, 165, 453 to `make build-server`/`/tmp/apiserver` as a manual pre-step or troubleshooting hint; **also** the Step 4d intro (~line 294, "a pre-built apiserver binary exists at `/tmp/apiserver`"), found only in review round 3 — update all to `/xce-build apiserver` and the Meson build-dir path.
- `src/tools/deploy-ha.sh` — the two `echo` strings in its `/tmp/apiserver` fallback branch (~162, 164) referencing `make build-server`; **also** the preceding block comment (~lines 138-155, "The apiserver is buildable TWO ways... (2) the standalone Go workflow (`cd src/frontend && make build-server`)..."), found only in review round 3 — same defect, one file over. The meson-first/`/tmp/apiserver`-fallback branching *logic* itself is unchanged (out of boundary — already correct); only text describing a now-deleted second build path changes.
- `src/frontend/docs/status.md` (line 67) — a status checklist line stating `Root Makefile: make test, make test-integration, make test-all` as current infrastructure; update to reflect Meson-only tooling.
- `src/docs/apiserver-backend-tasks.md` (lines ~70, ~120) — two `make test` references presented as current fact, not as a frozen spec record (this file lives outside `.kiro/specs/`, so Requirement 6.3's historical-spec exemption does not apply).
- `.claude/skills/xce-test-resources/SKILL.md` (line 108) — documents `$XCE_RESVERIFY_APISERVER_BIN`'s default as `/tmp/apiserver`; update alongside the script's own default change (5.1's discovery fix and its documentation must not disagree).
- `src/controller/testsuite/test_ha_bootstrap.c` (comments at ~lines 290, 338, 388) and `test_ha_ctrlhello.c` (comment at ~line 13) — each points a reader to "`make test-integration`" as where certain HA retry/tag/call-chain behavior is validated end-to-end. A 3-line rename to reference the regression suite directly (`tests/regression/run.sh`) rather than the deleted target; no test logic changes.
- Root `CLAUDE.md` (line 42) — the "Key Files" entry `` `src/frontend/Makefile` — primary build/test orchestration `` is deleted or replaced with the Meson-based equivalent (e.g. `src/tools/xce-build.sh` — already listed elsewhere in the same file's Build Commands).
- `src/frontend/apiserver/cmd/main.go` (~lines 25-27) and `src/frontend/xcecli/cmd/main.go` (~lines 39-41) — each has a comment reading "injected at link time via `-ldflags`... (see `src/tools/go_build` and `src/frontend/Makefile`'s `build-server`/`build-cli` target)"; drop the Makefile clause, keeping only `src/tools/go_build`.
- `src/frontend/tests/regression/run.sh` (line 69) — the `XCE` binary-not-found error currently says `"Build with: cd xcecli && CGO_ENABLED=0 go build -o /tmp/xce_static ./cmd/main.go"`, reproducing the deleted `build-cli` target's exact manual recipe (5.2's own literal example of what must not remain). Update to name `/xce-build apiserver` (which also builds `xcecli`) instead. The `XCE="${XCE:-/tmp/xce_static}"` default (line 12) is left as an inert placeholder — callers are still expected to pass `XCE=` explicitly (testing.md already documents this as a required env var), so only the error-message *content* needs to stop pointing at a deleted target.
### Deleted Files
- `src/frontend/Makefile` — removed only after the above are landed and verified (see Migration Strategy).
### Unchanged (verified, not modified)
- `src/frontend/tests/regression/run.sh`'s core logic (still a standalone, parameterized script driven by `XCE`/`URL` env vars) — only its not-found error message changes, per above. The Makefile's `test-integration` target added no logic beyond renaming `XCE_BIN`→`XCE`/`SRV_URL`→`URL`; once removed, callers invoke `run.sh` directly with those two env vars, pointed at a Meson-built `xcecli` binary (`<build_dir>/frontend/xcecli/xcecli`) instead of the Makefile's `build-cli` output.
- `src/tools/go_build`, `apiserver/meson.build`'s existing `custom_target`, `xcecli/meson.build` — unchanged.
## System Flows
The Before/After flow is fully captured in the Architecture diagram above; no additional sequence diagram is needed — this is a call-site substitution, not a new multi-party interaction.
## Requirements Traceability
| Requirement | Summary | Components | Flows |
|-------------|---------|------------|-------|
| 1.1, 1.2, 1.3 | Single build path; one fingerprint source; Makefile deleted once 2-4 covered | `xce-build.sh` (build already Meson-only), Makefile deletion | Migration Strategy Phase 4 |
| 2.1, 2.2, 2.3 | Unit tests run without Makefile, same behavior, clear failure reporting | `xce-build.sh` direct `go test` call sites | Migration Strategy Phase 2 |
| 3.1, 3.2, 3.3 | CGO backend suite runnable without Makefile, single CGO derivation, no coverage regression | `apiserver/meson.build` new `test()` | Migration Strategy Phase 1 |
| 4.1, 4.2, 4.3 | Integration suite targets canonical-build binary, same scope/pass-fail criteria | `tests/regression/run.sh` invocation convention (unchanged script) | Migration Strategy Phase 3 |
| 5.1, 5.2, 5.3 | Binary discovery from canonical build dir; clear error naming the surviving command; no sole dependency on fixed legacy path | `run-resource-verify.sh` `APISERVER_BIN` fix, `deploy-ha.sh` message strings | Migration Strategy Phase 3 |
| 6.1, 6.2, 6.3 | Live docs reference only the surviving system; historical docs untouched | 11 doc files (plus 2 C testsuite and 2 Go source comment fixes) listed in File Structure Plan | Migration Strategy Phase 3 |
## Components and Interfaces
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
|-----------|--------------|--------|---------------|---------------------------|-----------|
| `xce-build.sh` unit-test invocation | Build tooling | Run apiserver/xcecli unit tests without Make | 2.1, 2.2, 2.3 | `go` toolchain (P0) | Batch |
| `apiserver/meson.build` backend test() | Build tooling | Run CGO backend suite via Meson, reusing existing CGO derivation | 3.1, 3.2, 3.3, 1.2 | `apiserver_cgo_deps` (P0), `go_compiler` (P0), `libxce` (P0) | Batch |
| `run-resource-verify.sh` binary discovery | Verification tooling | Locate the apiserver binary from the canonical build dir | 5.1, 5.2, 5.3 | Meson build dir layout (P0) | Batch |
| `tests/regression/run.sh` invocation convention | Verification tooling | Run the E2E regression suite against a canonical-build binary | 4.1, 4.2, 4.3 | Meson-built `xcecli` binary (P0), live apiserver (P0) | Batch |
| `deploy-ha.sh` message strings | Verification tooling | Stop naming a deleted Makefile target in fallback/warning text | 5.2, 5.3, 6.2 | — | — |
| Documentation set (11 files) | Docs | Reference only the surviving build system | 6.1, 6.2, 6.3 | — | — |
Only the backend `test()` registration introduces a genuinely new boundary (a new Meson test target with its own env/dependency contract); the rest are call-site substitutions in already-existing scripts, so they are covered by the summary table plus the File Structure Plan detail above rather than full component blocks.
### Build Tooling
#### apiserver backend test() target
| Field | Detail |
|-------|--------|
| Intent | Run the CGO-backed `apiserver/backend/...` test suite under Meson, with zero independent CGO-flag derivation |
| Requirements | 3.1, 3.2, 3.3, 1.2 |
**Responsibilities & Constraints**
- Owns: registering the test with Meson's test runner (`suite: 'apiserver'`) so `meson test -C <dir> --suite apiserver` (and full `meson test`) picks it up.
- Does not own: the CGO flag values themselves — reads `apiserver_cgo_deps`'s already-computed `cgo_cflags`/`cgo_ldflags`, the same values the existing `custom_target('apiserver', ...)` in the same file already uses.
- Must not add `-race` — confirmed unusable in this project's ASAN-linked CGO environment (`component-failover-adoption/tasks.md:197`, hit 3 times independently).
**Dependencies**
- Inbound: `meson test` / `/xce-build apiserver`'s test-running step (P0)
- Outbound: `apiserver_cgo_deps` derivation, `libxce` build target (P0 — must build before this test can link)
- External: `go` toolchain via `go_compiler` program object (P0)
**Contracts**: Batch [x]
##### Batch / Job Contract
- Trigger: `meson test` (any invocation covering the `apiserver` suite, including bare `meson test -C <dir>`)
- Input / validation: requires `libxce.a` and its generated headers already built in the same build dir (`depends: [libxce]` enforces build ordering)
- Output / destination: standard Meson test log; non-zero exit on any `go test` failure
- Idempotency & recovery: `go test -count=1` (no test caching across runs, matching every other Go test invocation in this codebase)
**Implementation Notes**
- Integration: env additionally sets `GOFLAGS=-mod=vendor`, `GOPROXY=off`, and writable `GOCACHE`/`GOPATH` under the build dir's own tmp space — mirroring `tools/go_build`'s existing hermetic/offline conventions, since this is the first place `apiserver/backend`'s real CGO path is exercised from inside the Meson/Nix sandbox rather than a developer's ambient shell.
- Validation: confirm `meson test -C <dir> --suite apiserver` (both standalone and as part of `/xce-build apiserver`'s existing test step) reports this new test, and that it fails for the expected reason if `apiserver_cgo_deps` is temporarily broken (proving it isn't silently skipped).
- Risks: this is the one genuinely new capability in the migration — its correctness (does it actually build+run `backend/clib.go`'s real CGO path, not the `!cgo` stub) is the primary thing Testing Strategy below must prove before Migration Strategy Phase 4 (Makefile deletion) is safe.
## Testing Strategy
- **Unit-level (invocation correctness)**: after migrating `xce-build.sh`'s two call sites, confirm `/xce-build apiserver` and `/xce-build all` still run exactly the same apiserver/xcecli test package set (`./store/... ./utils/... ./controllers/... ./backend/...` for apiserver, `./...` for xcecli) with the same pass/fail counts as the pre-migration `make test`-driven baseline.
- **New capability verification**: run `meson test -C <dir> --suite apiserver` (and full `/xce-build apiserver`) and confirm the new backend test target appears, links against real `libxce` (not the `//go:build !cgo` stub), and passes with no compiler/linker error referencing a literal `@CURRENT_SOURCE_DIR@`/`@BUILD_ROOT@` string (the specific failure mode of an unresolved placeholder token reaching `go test`); then deliberately break `apiserver_cgo_deps` (e.g. comment out `librtti_dep`) and confirm the new test fails for the expected linker/undefined-symbol reason — proving it exercises the real CGO path, not a vacuous pass.
- **Discovery-path regression**: with `src/frontend/Makefile` deleted, run `run-resource-verify.sh` end-to-end and confirm `APISERVER_BIN` resolves to the Meson build dir and the apiserver layer is reachable; confirm the failure message (binary artificially removed) names `/xce-build apiserver`, not a deleted Makefile target.
- **E2E regression parity**: run `tests/regression/run.sh` directly (`XCE=<meson-built xcecli path> URL=<live apiserver> bash tests/regression/run.sh`) against a live apiserver and confirm the same PASS/FAIL/SKIP counts as the pre-migration `make test-integration` baseline.
- **Live-reference sweep**: run BEFORE Phase 4 (Makefile deletion), scoped to each file named in File Structure Plan, and again as a full-repository pass after — grep for `make (build-server|build-cli|test|test-backend|test-integration|test-all)` AND `/tmp/apiserver`/`/tmp/xce_static`. Running it per-file before deletion (not only repo-wide after) is what makes the File Structure Plan's "every live reference, not just the enumerated ones" scope rule actually enforceable, rather than relying on the enumeration being exhaustive. The full-repository pass will legitimately still hit files that are not violations — name these exclusion classes explicitly rather than leaving "zero hits" as an unachievable, ambiguous bar an implementer would otherwise have to rediscover by judgment call:
- `.kiro/specs/**` — historical spec documents (Requirement 6.3's own exemption), including this spec's own `brief.md`/`research.md`/`requirements.md`/`design.md`, which necessarily discuss the very targets being removed.
- `**/vendor/**` — third-party vendored dependency files (e.g. `go.uber.org/zap`'s own `CONTRIBUTING.md`) whose `make test` refers to that upstream project, not XCE's.
- Dated historical records: `CHANGES.md`, `reports/*.md`, `docs/superpowers/plans/*.md`, `src/docs/archive/**` — frozen snapshots of past state, not live guidance.
- `.kiro/steering/roadmap.md`'s entry for this very spec (~line 82) — describes this spec's own backlog/discovery history, not a live instruction.
- `.wolf/memory.md`, `.wolf/cerebrum.md`, `.wolf/buglog.json` — OpenWolf's own append-only session/bug logs, not authoritative project documentation.
- `src/frontend/tests/regression/run.sh:12` — the `XCE="${XCE:-/tmp/xce_static}"` default is a deliberately-retained inert placeholder (see File Structure Plan); only its *error-message content* (line 69) is required to change, not this default value itself.
Any hit NOT falling into one of the above classes is a real gap and must be fixed before Phase 4.
## Migration Strategy
Phased, coverage-preserving order — the CGO backend test gap (Phase 1) must close before the Makefile can be safely removed (Phase 4), matching Requirement 3.3's explicit non-regression constraint.
```mermaid
flowchart LR
P1[Phase 1: add Meson backend test target] --> P3[Phase 3: fix run-resource-verify.sh discovery + update docs + document run.sh invocation]
P2[Phase 2: migrate xce-build.sh unit-test call sites] --> P3
P3 --> P4[Phase 4: delete src/frontend/Makefile]
```
- **Phase 1** (Requirement 3) and **Phase 2** (Requirement 2) have no dependency on each other — different files, no shared state — and may be implemented and landed in parallel. Phase 1 is net-new capability, zero risk to existing behavior. Phase 2 mirrors the already-proven `xcecli` pattern; low risk.
- **Phase 3** (Requirements 4, 5, 6): depends on both Phase 1 and Phase 2, since the documentation this phase writes describes the final Makefile-free state of both the build and the backend test target. Mechanical once Phases 1-2 land — the real reference list and exact line numbers are already enumerated in File Structure Plan.
- **Phase 4** (Requirement 1): the only phase that is hard to reverse cheaply — gated on Phases 1-3 having landed and their own verification (Testing Strategy above) having passed.
- **Rollback trigger**: if Phase 1's backend test reveals `apiserver_cgo_deps` cannot be cleanly shared between the `custom_target` and the new `test()` (e.g. a Meson version limitation on reusing computed variables across targets in the same file), stop before Phase 4 and re-evaluate Requirement 3.2 rather than forcing a second, duplicate CGO-flag derivation into existence — that would defeat this spec's own purpose.
- **Validation checkpoint**: run the full Testing Strategy section after Phase 3, before executing Phase 4.