myxce/.kiro/specs/eliminate-frontend-makefile/research.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

15 KiB

Summary

  • Feature: eliminate-frontend-makefile
  • Discovery Scope: Extension (existing build system, light discovery — brief.md's own Path C investigation already covered most of the ground; this pass verified currency and filled architecture-level gaps)
  • Key Findings:
    • Only one live automated caller of src/frontend/Makefile exists today: xce-build.sh's two cd frontend && make test call sites (apiserver and all cases). build-cli/build-server/test-backend/test-integration have no live automated caller — they are developer-manual-only.
    • apiserver/meson.build already computes the exact CGO flags (apiserver_cgo_deps: librtti, c-capnproto, openssl, tinylog, math, rt) the Makefile's test-backend independently re-derives via pkg-config. A shared derivation is directly reusable for a new test() target — no new mechanism needed.
    • xcecli's case in xce-build.sh already bypasses Make entirely (direct go test invocation) — this is a live, working precedent for the apiserver unit-test migration, not a hypothetical pattern.
    • run-resource-verify.sh already has an established $BUILD_DIR/<component>/<binary> discovery convention for its other three binaries (BINARY, SCHED_BIN, XT); only APISERVER_BIN is the odd one out still hardcoded to /tmp/apiserver.
    • tests/regression/run.sh is already a standalone, fully-parameterized script (XCE/URL env vars). The Makefile's test-integration target added zero logic beyond an env-var rename (XCE_BIN→XCE, SRV_URL→URL) plus a build-cli prerequisite. No new script is needed for Requirement 4 — only documentation of the direct invocation.

Research Log

Live automation dependency surface

  • Context: requirements.md's Boundary Context needed to state precisely what currently depends on the Makefile, to scope the coverage-preservation requirements accurately.
  • Sources Consulted: grep -rn --include="*.sh" -E '\bmake\b' src/tools/ .claude/skills/, direct read of xce-build.sh lines 255-328.
  • Findings: Exactly 2 live make invocations exist in the whole repo's live automation (xce-build.sh:275, :322, both cd frontend && make test). deploy-ha.sh and run-resource-verify.sh reference the Makefile only in human-facing strings (error/log messages) or by checking for its output path, never by executing make.
  • Implications: The coverage-preservation requirements (2, 3, 4) protect manual developer workflows and documentation accuracy, not any existing automated pipeline — this made scope smaller and safer than it might first appear, and confirmed as a direct answer to a user clarifying question during the requirements phase.

CGO backend test flag derivation reuse

  • Context: requirements.md 3.2 requires the new backend test path to derive CGO flags from "the same single source" as Meson's existing apiserver build, not a second independent derivation (the exact failure mode this whole spec exists to eliminate).
  • Sources Consulted: apiserver/meson.build (full read), src/meson.build (go_compiler = find_program('go'), pkgconfig = find_program('pkg-config')), src/frontend/Makefile's own test-backend recipe (FRONTEND_CGO_PKGS := librtti c-capnproto openssl tinylog, -lm -lrt).
  • Findings: apiserver/meson.build's apiserver_cgo_deps loop already produces cgo_cflags/cgo_ldflags covering the identical dependency set the Makefile independently re-derives via pkg-config. go_compiler (the raw go program object) is already available at top-level scope — no new find_program() call needed.
  • Implications: A new test() in the same file can read the same cgo_cflags/cgo_ldflags variables the existing custom_target already computes — genuinely one derivation, not two, satisfying 3.2 directly rather than by convention/discipline.

run-resource-verify.sh's own established discovery pattern

  • Context: requirements.md 5.1-5.3 needed a concrete replacement for run-resource-verify.sh's /tmp/apiserver default.
  • Sources Consulted: full grep of _BIN=/BUILD_DIR in run-resource-verify.sh.
  • Findings: BINARY, SCHED_BIN, XT already resolve as "${ENV_VAR_OVERRIDE:-$BUILD_DIR/<component>/<binary>}", each independently overridable, with $BUILD_DIR defaulting to $REPO/src/xce.build. Only APISERVER_BIN deviates, hardcoded to /tmp/apiserver with no $BUILD_DIR-relative default at all.
  • Implications: the correct fix is not "mirror deploy-ha.sh's dual-path pattern" as brief.md's Downstream section literally suggested, but "match this script's own sibling convention exactly" — see Design Decision below.

Architecture Pattern Evaluation

Option Description Strengths Risks / Limitations Notes
Meson test() reusing apiserver_cgo_deps New backend test target shares the existing custom_target's CGO derivation Single source of truth (satisfies 3.2 directly); no wrapper script Requires the derivation to be readable by both a custom_target and a test() in the same meson.build — confirmed straightforward, both are just Meson variables in scope Selected
Standalone shell script re-deriving CGO flags via pkg-config Keep a Makefile-free script mirroring the Makefile's own test-backend pkg-config derivation Avoids touching apiserver/meson.build Reintroduces exactly the duplicate-derivation risk this spec exists to eliminate Rejected (brief.md's own "not recommended" option 2)

Design Decisions

Decision: run-resource-verify.sh's APISERVER_BIN follows its own sibling convention, not deploy-ha.sh's dual-path fallback

  • Context: brief.md's Downstream section suggested mirroring deploy-ha.sh's meson-first / /tmp/apiserver-fallback pattern.
  • Alternatives Considered:
    1. Mirror deploy-ha.sh's dual-path pattern (meson path first, /tmp/apiserver fallback).
    2. Match run-resource-verify.sh's own existing BINARY/SCHED_BIN/XT convention (single $BUILD_DIR-relative default, no fallback).
  • Selected Approach: Option 2.
  • Rationale: once src/frontend/Makefile is deleted, nothing in the project's own tooling will ever populate /tmp/apiserver again — a fallback to it is a fallback to a path only a developer's own hand-run go build could produce, which is not a real safety net worth the inconsistency of being the only binary in this script with different default-resolution logic than its three siblings.
  • Trade-offs: slightly diverges from the brief's literal suggestion, but produces a more internally consistent script and satisfies requirements.md 5.3 ("shall not require... as its only means") without needing a fallback at all, since the primary path is now proven reliable (Meson always builds apiserver via /xce-build apiserver/all).
  • Follow-up: deploy-ha.sh's own /tmp/apiserver fallback becomes similarly vestigial once the Makefile is gone — out of boundary for this spec (already migrated in a prior session per brief.md), but worth flagging as a small future cleanup, not a blocker here.

Decision: No wrapper script for the new backend test target

  • Context: Meson test() needs an executable; go test with CGO env and a specific working directory could be wrapped in a small shell script for clarity.
  • Alternatives Considered:
    1. A thin wrapper shell script invoking go test with the right env, registered as the test()'s executable.
    2. test() invokes the go_compiler program object directly, with args, env, and workdir kwargs.
  • Selected Approach: Option 2.
  • Rationale: Meson's test() natively supports workdir and env; no wrapper is needed, and one fewer file avoids exactly the kind of incidental indirection this spec exists to remove elsewhere.
  • Trade-offs: revised after an independent adversarial review round (2026-09-03) caught a real one: cgo_cflags/cgo_ldflags contain literal @CURRENT_SOURCE_DIR@/@BUILD_ROOT@ placeholder text that is normally resolved by tools/go_build's own fix_env sed machinery — bypassing go_build means bypassing that resolution too. Fixed by resolving both tokens directly with Meson's own .replace() string method at configure time (cgo_cflags.replace('@CURRENT_SOURCE_DIR@', meson.current_source_dir()).replace('@BUILD_ROOT@', meson.project_build_root())) rather than reintroducing a wrapper script — still net simpler than Option 1, but not the zero-cost choice the first design pass assumed.

Decision: deploy-ha.sh's dead-target message strings are in boundary even though its discovery logic is not

  • Context: the first design draft excluded deploy-ha.sh entirely as "already migrated in a prior session," reasoning from the binary-discovery logic (genuinely already meson-first). An independent adversarial review round caught that its three make build-server//tmp/apiserver-referencing echo strings (lines ~157-164) were not part of that prior migration and would still tell a developer to run a target that no longer exists, once src/frontend/Makefile is deleted.
  • Selected Approach: split the file's two concerns — discovery logic (out of boundary, unchanged) vs. message text (in boundary, added to File Structure Plan) — rather than treating "already touched once" as "fully out of scope."
  • Follow-up: none needed beyond the File Structure Plan entry; this is a 2-string text edit, not a logic change.

Decision: status.md and apiserver-backend-tasks.md are live documentation, not historical spec records

  • Context: requirements.md 6.3 exempts historical, already-completed spec documents from doc updates. An independent adversarial review round found two files outside .kiro/specs/ (src/frontend/docs/status.md:67, src/docs/apiserver-backend-tasks.md:70,120) stating make test/make test-integration as current fact — missed by the original brief's doc inventory, which only enumerated files already known to be live-authoritative.
  • Selected Approach: treat both as live documentation in scope for update, since 6.3's exemption is worded specifically around ".kiro/specs" historical records, not arbitrary status/changelog files elsewhere in the repo.
  • Follow-up: added both to File Structure Plan.

Decision: doc-file File Structure Plan entries state a scope rule, not a closed line-number list

  • Context: three successive independent adversarial review rounds each found at least one live make-target/fixed-path reference in a file the design already had in scope, in a section/line the enumeration hadn't named (round 1: two new doc files entirely; round 2: xce-test-resources/SKILL.md, run.sh's error message; round 3: tech.md:81-82, testing.md:95, deploy-ha.sh:138-155's comment block, xce-deploy-ha/SKILL.md:294 — all in files already listed). A closed line-number enumeration kept being treated as if it were the completeness boundary, when it was really just a snapshot of what one investigation pass happened to find.
  • Selected Approach: reworded every doc/script File Structure Plan entry around a stated scope rule ("every live reference in this file," not just the enumerated lines) and moved the Testing Strategy's live-reference sweep to run per-file BEFORE Phase 4, not only repo-wide after — so a task implementing one file is verifiably complete against that file's own grep, independent of whether this design's own enumeration was exhaustive.
  • Rationale: the recurring pattern strongly suggested the enumeration approach itself, not any individual miss, was the actual defect — fixing instances one at a time without changing the underlying acceptance criterion risked a fourth, fifth round finding the same class of gap indefinitely.
  • Follow-up: a fourth review round found the sweep's own "zero hits" bar was itself unachievable as worded (several legitimate, deliberately-excluded classes — vendored files, dated historical records, an inert placeholder default, OpenWolf's own logs — all match the same grep). Named each exclusion class explicitly in the Testing Strategy rather than leaving "zero hits" ambiguous; also found two C testsuite files (test_ha_bootstrap.c, test_ha_ctrlhello.c) with comments naming make test-integration by name, outside any doc/script file category considered so far — added to File Structure Plan (a 3-line rename, not a logic change). A fifth review round, specifically scanning .go source-file comments (a category rounds 1-4 never checked), found two more: the ROOT CLAUDE.md's "Key Files" entry (line 42, the single most-visible live reference in the repo — loaded into every session, distinct from the already-covered src/frontend/CLAUDE.md) and both Go modules' cmd/main.go ldflags-injection comments naming the Makefile's build-server/build-cli targets. Both added to File Structure Plan. The recurring lesson across rounds 1, 4, and 5: each round's "fresh eyes, no assumptions" instruction and switch to an unchecked file category (docs → scripts → C test comments → Go source comments) is what surfaces genuinely new gaps — re-reading the same already-covered files more carefully stopped finding anything past round 2.

Risks & Mitigations

  • Risk: the new backend test() silently links against backend/stub.go (the //go:build !cgo fallback) instead of the real CGO path, producing a vacuous pass. Mitigation: Testing Strategy's "New capability verification" step deliberately breaks apiserver_cgo_deps and confirms the test fails for the expected linker reason before trusting a green run.
  • Risk: deleting the Makefile before all manual-developer-only targets (test-backend, test-integration) have a proven equivalent silently drops coverage nobody notices until someone needs it. Mitigation: Migration Strategy's explicit Phase ordering (1-3 must land and be verified before Phase 4).
  • Risk: run-resource-verify.sh's new discovery default assumes $BUILD_DIR ($REPO/src/xce.build, the asan profile dir) always contains a built apiserver — true only after /xce-build apiserver/all has actually been run. Mitigation: this matches the existing behavior/risk profile of the script's other three binaries (BINARY/SCHED_BIN/XT), which already assume the same; no new risk class introduced.

References

  • .kiro/specs/eliminate-frontend-makefile/brief.md — original Path C discovery investigation (2026-08-27), the primary source for scope/seam decomposition.
  • src/tools/xce-build.sh (lines 255-328) — live call-site evidence.
  • src/frontend/apiserver/meson.build, src/meson.build — CGO derivation and program-object reuse evidence.
  • .claude/skills/xce-test-resources/run-resource-verify.sh (lines 200-207, 348-351) — existing sibling discovery convention.