myxce/.kiro/specs/xce-harness-component/research.md
Rongsong Shen b3aa2af242
test(xce-harness-component): verify build isolation; narrow requirement 2.2
Validation task 4.3 found a real, deterministic conflict with the
original requirement 2.2 wording, not a flake: /xce-build controller
alone's default asan profile transitively rebuilds harness/xce_test,
because Meson's test-runner rebuilds every registered test's build-
order dependencies project-wide before filtering by --suite, and
harness_unit_test (task 2.1) depends on it. Confirmed via build-log
step-count analysis (explicit compile: 209 steps, clean; subsequent
meson test: 844 steps, resurrects it) and independently reproduced
twice, with an exact step-count match both times.

User decision (2026-09-14): accept as a documented Meson limitation
rather than auditing every component's test-target list (controller's
own suite already relies on this same mechanism for 4 of its own test
binaries) or weakening harness_unit_test's build-order guarantee
(verified load-bearing in task 4.2). Requirement 2.2 narrowed to what
the build system directly controls: its explicit compile-target list
never names xce_test. requirements.md/design.md/tasks.md updated to
match and cross-reference each other; logged as bug-1806 (accepted,
not fixed) for the record.

Also commits the xce-harness-component spec's remaining docs
(requirements.md, brief.md, research.md, spec.json) and the roadmap.md
promotion entry from discovery, none of which had been committed yet.

Task 4.3 of xce-harness-component.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
2026-09-14 09:59:15 +00:00

17 KiB

Research & Design Decisions

Summary

  • Feature: xce-harness-component
  • Discovery Scope: Extension (existing system) — Light Discovery
  • Key Findings:
    • 14 of xce_test's 15 source files have zero dependency on controller-private headers or sources; only ctrlstatus_fmt.h is genuinely shared with controller, and it is header-only (static inline), so it can be promoted to a neutral shared location instead of creating a cross-component dependency in either direction.
    • Corrected during /kiro-validate-design review: only 13 of the 17 xce_test-related test files actually live inside the single controller_unit_test binary (test_waldump.c through test_test_cli.c). The other 4 — test_resval_typeresolve.c, test_joblist_display.c, test_join_worker_args.c, test_listrow_json.c — are already separate standalone executables in src/controller/meson.build (resval_typeresolve_unit_test, joblist_display_unit_test, join_worker_args_unit_test, listrow_json_unit_test), each split out specifically because they have "zero dependency on controller.c/cluster.c internals" — the same property verified for all 15 xce_test production files. User decision: preserve all 5 as 5 separate binaries after the move (no consolidation) — see Design Decisions.
    • Two of the 13 files in the main group (test_test_cli.c, test_xcetest_process_entry.c) are process-spawning integration tests: they locate and exec the actual built xce_test binary over a pty (clitest_spawn/resolve_xce_test_path), not white-box linkage. The new harness_unit_test binary (the one genuinely new binary — the other 4 relocate verbatim) therefore has a genuine build-order dependency on the xce_test executable target — a pattern controller_unit_test never needed (it never had to build another executable as its own prerequisite).
    • controller_test.c (src/controller/testsuite/controller_test.c) is the master Unity runner for the 13-file group — confirmed by reading it directly: it declares one extern/RUN_TEST pair per test function (lines 68-80 for the 13 moving functions; test_ctrlstatus at line 64 stays, since it tests controller's own ctrlstatus.c). This file must be edited, not just the meson source lists — an item the original design pass missed entirely.
    • The exact site to decouple the build is concrete and single-line: src/tools/xce-build.sh:295's controller) case compiles xce_controller controller_unit_test controller/xce_test — controller/xce_test is the one target to drop, and a new harness) case follows the identical pattern already used by scheduler)/worker).
    • Four downstream skills assume xce_test exists once controller is built: xce-test-ha (run-ha-soak.sh), xce-deploy-ha (SKILL.md, soft-skip pattern), xce-test-multihost (run-multihost-verify.sh), xce-test-resources (run-resource-verify.sh) — each needs an explicit harness-build step or prerequisite check.

Research Log

Controller-dependency audit of xce_test's 15 source files

  • Context: Requirement 1.4 requires the harness to have no dependency on controller-private sources/headers; this must be verified, not assumed.
  • Sources Consulted: grep -h '^#include "' across all 15 xce_test_src files (test.c, waldump.c, walcompare.c, snapverify.c, snapdiff.c, configcompare.c, resval_typeresolve.c, joblist_display.c, join_worker_args.c, rcfile.c, flagspec.c, interruptcall.c, confirm.c, listfmt_json.c, listrow_json.c).
  • Findings: Every include resolves to either a libxce public header (src/include/*.h: client.h, config.h, delay.h, idgen.h, persist.h, preason.h, utils.h, wal_types.h, xce/cluster.h, xce/types.h), the generated controller.capnp.h schema header, xce_test's own component-local headers (14 of the 15, one-to-one with the .c files, plus xcetest_interrupt.h), or the one shared exception below (ctrlstatus_fmt.h).
  • Implications: The move is a pure relocation for 14/15 files — no header untangling needed. Only ctrlstatus_fmt.h requires an explicit decision (see Decision below).

ctrlstatus_fmt.h shared-header investigation

  • Context: #include "ctrlstatus_fmt.h" appeared in both an xce_test file (joblist_display.c) and a controller test file — needed to confirm whether this is a real controller-private dependency (which would violate Requirement 1.4) or something safely shareable.
  • Sources Consulted: grep -rl "ctrlstatus_fmt.h" across src/controller/*.c and src/controller/testsuite/*.c; read the function definition.
  • Findings: ctrlstatus_fmt.h declares exactly one function, ctrlstatus_format_time_ms(), as static inline (ctrlstatus_fmt.h:33) — header-only, no .c implementation, no link-time dependency. Four consumers: test_ctrlstatus.c (controller's own white-box test — stays with controller), and joblist_display.c / test.c / test_joblist_display.c (all three move to harness). No production controller .c file includes it.
  • Implications: Because it is header-only, it can move to a neutral shared location without creating a link dependency in either direction. See Decision below.

test_test_cli.c / test_xcetest_process_entry.c linkage mechanism

  • Context: These two files are part of controller_unit_test_src but have no matching production .c counterpart (unlike the other 14 pairs) — needed to understand how they test xce_test if test.c itself isn't recompiled into controller_unit_test.
  • Sources Consulted: Read test_test_cli.c (clitest_spawn/clitest_spawn_ex/resolve_xce_test_path) and grep for resolve_xce_test_path across the testsuite.
  • Findings: test_test_cli.c defines resolve_xce_test_path() and clitest_spawn()/clitest_spawn_ex() itself — these locate the already-built xce_test binary on disk and fork/exec it under a pty, then drive it as a real subprocess (matching bug-1792's fix in .wolf/buglog.json, which touched this exact spawn path). test_xcetest_process_entry.c reuses resolve_xce_test_path via an extern declaration into the same controller_unit_test binary (both are linked into one executable today, so extern resolves within that one binary).
  • Implications: These are integration tests wearing a unit-test binary's clothes. The new harness unit-test binary must build (or otherwise guarantee the existence of) the xce_test executable before running these two tests — a real build-order dependency to declare explicitly in src/harness/meson.build, not an incidental one.

Downstream consumer inventory

  • Context: Requirement 5 requires every workflow that assumes "building controller also builds xce_test" to keep working once that coupling is removed.
  • Sources Consulted: grep -rl "xce_test\|xce-build controller" across the four named skills' directories.
  • Findings: xce-test-ha/run-ha-soak.sh + SKILL.md, xce-deploy-ha/SKILL.md (no script of its own — /xce-deploy-ha deploys already-built binaries and soft-skips missing ones per build-deployment.md), xce-test-multihost/run-multihost-verify.sh + SKILL.md, xce-test-resources/run-resource-verify.sh + SKILL.md.
  • Implications: Three shell scripts plus four SKILL.md docs are the concrete edit surface for Requirement 5; xce-deploy-ha needs only its soft-skip documentation/message updated (mirroring its existing worker/scheduler/xceadmin soft-skip pattern), not a new build step of its own.

xce-build.sh decoupling site

  • Context: Needed the exact, current mechanism coupling controller and xce_test builds to plan the decoupling precisely rather than guessing at meson internals.
  • Sources Consulted: Read src/tools/xce-build.sh:282-340 (the case "$COMPONENT" in ... esac dispatch).
  • Findings: controller) (lines 292-299) runs meson compile ... xce_controller controller_unit_test controller/xce_test then meson test --suite controller. Sibling cases (scheduler), worker)) follow an identical three-part pattern: setup_build, an explicit meson compile target list, then a conditional meson test --suite <name>. all) (not shown here, documented in build-deployment.md) compiles with no explicit target list, so it will automatically pick up a new harness target once src/meson.build gains subdir('harness') — no all) case change needed.
  • Implications: The fix is mechanical and low-risk: drop controller/xce_test from line 295, and add a new harness) case built from the same three-part template already proven by scheduler)/worker).

Architecture Pattern Evaluation

Option Description Strengths Risks / Limitations Notes
Nested src/ per structure.md's general pattern (src/harness/src/*.c, src/harness/include/*.h) Follow the worker/scheduler layout exactly Consistent with the documented general pattern xce_test's files were already flat siblings in testsuite/; adding a nesting layer is pure churn with no benefit for a single-purpose CLI tool Rejected — no multi-domain internal structure to justify the extra nesting
Flat root, controller-exception style (src/harness/*.c, src/harness/include/*.h, src/harness/testsuite/*.c) Mirror controller's own documented layout exception Matches the closest sibling in both size and shape (single-purpose C program, not a multi-file daemon); minimal file churn (headers/.c files keep flat sibling relationships they already have) Diverges from the "general" worker/scheduler pattern, but that pattern is documented as having an existing exception already (controller) Selected

Design Decisions

Decision: Directory layout mirrors controller's flat-root exception, not the nested worker/scheduler pattern

  • Context: structure.md's C Daemon Layout Pattern nests implementation under src/<component>/src/, but documents controller/ as an explicit exception that keeps .c files flat in the component root while still having its own testsuite/.
  • Alternatives Considered: (1) nested src//include/ per the general pattern; (2) flat root matching controller's exception.
  • Selected Approach: src/harness/*.c + src/harness/include/*.h for production sources (the 15 files), src/harness/testsuite/*.c for the 17 white-box/integration test files plus a new Unity runner.
  • Rationale: xce_test's files are already flat siblings today; nesting them would be churn without a corresponding benefit, since there is no multi-domain internal structure (unlike worker/scheduler, which separate plugin/provider subsystems).
  • Trade-offs: A future reader must know controller and harness are the two flat-root exceptions to the general pattern — mitigated by recording both in structure.md's Exceptions list (Requirement 6.3).
  • Follow-up: None.

Decision: Promote ctrlstatus_fmt.h to src/include/xce/

  • Context: It is the one header genuinely shared between controller (test_ctrlstatus.c) and harness (joblist_display.c, test.c, test_joblist_display.c); leaving it in either component's tree would make the other component depend on it.
  • Alternatives Considered: (1) leave it in src/controller/testsuite/, have harness include it via a relative path back into controller (violates Requirement 1.4's "no dependency on any controller-private header" literally, and creates a harness→controller direction the standalone-component goal is meant to eliminate); (2) move it to src/controller/include/ (controller's real component-header dir) and have harness depend on that explicitly as an allowed exception; (3) promote it to bare src/include/ (the libxce public-header boundary); (4) promote it to src/include/xce/ (the existing small subset of cross-daemon shared headers — cluster.h, types.h, errnox.h, client_failover.h, cluster_def.h — distinct from libxce's own API headers).
  • Selected Approach: (4) — move to src/include/xce/ctrlstatus_fmt.h, update its three moving consumers plus controller's test_ctrlstatus.c to include it as xce/ctrlstatus_fmt.h.
  • Rationale: It is header-only (static inline, zero link surface) and used for display formatting, not controller business logic, so it fits a shared boundary cleanly and satisfies Requirement 1.4 literally rather than by exception. Raised as a /kiro-validate-design critical issue (bare src/include/ reads as libxce's real runtime API surface, which this isn't) — resolved to the existing xce/ prefix instead, which is explicitly precedented for exactly this kind of small, cross-daemon, non-core-API shared header.
  • Trade-offs: Nominally grows src/include/xce/'s surface by one small formatting helper; judged acceptable given the existing precedent and that it removes a real cross-component coupling.
  • Follow-up: None.

Decision: Keep the 5 harness test binaries separate rather than consolidating

  • Context: /kiro-validate-design review found the original design's "one new harness_unit_test binary" model was factually incomplete — 4 of the 17 test files are already independent binaries in src/controller/meson.build, split out because they have no dependency on controller internals. Once relocated, that original isolation reason no longer distinguishes them from the rest of harness (everything in harness has that same property), which made consolidation into one binary a plausible simplification.
  • Alternatives Considered: (1) consolidate all 17 files into one harness_unit_test binary; (2) preserve today's 5-binary structure verbatim, just relocated and re-suited.
  • Selected Approach: (2) — user decision.
  • Rationale: Lower risk and higher file-for-file fidelity to the current, working structure; avoids introducing a new merge step (and its own risk of a missed extern/RUN_TEST wiring mistake) into a spec whose entire purpose is a low-risk relocation.
  • Trade-offs: Forgoes a small simplification opportunity (one binary instead of five); judged acceptable given this spec's goal is decoupling, not build consolidation.
  • Follow-up: None — the 4 standalone binaries relocate with no internal changes beyond their suite: tag and include/dependency paths.

Decision: harness_unit_test declares an explicit build-order dependency on the xce_test executable

  • Context: test_test_cli.c and test_xcetest_process_entry.c spawn the built xce_test binary as a subprocess; they will silently exec a stale or missing binary if the harness's own build doesn't guarantee xce_test is built first.
  • Alternatives Considered: (1) leave build ordering implicit (risk: flaky/stale-binary test runs); (2) declare the dependency explicitly in src/harness/meson.build so meson's own DAG enforces it.
  • Selected Approach: (2).
  • Rationale: This is exactly the kind of implicit-but-load-bearing coupling this spec exists to eliminate elsewhere (controller↔harness); introducing a new implicit one inside harness itself would be inconsistent.
  • Trade-offs: None material — this is the same pattern meson already supports for any test that needs another target built first.
  • Follow-up: Verify during implementation that the two spawning tests fail loudly (not silently pass against a stale binary) if the dependency were ever removed by accident — worth one regression test.

Risks & Mitigations

  • Risk: A downstream skill script is missed, silently breaking a workflow the first time someone runs it without having built harness. — Mitigation: Requirement 5.4 requires a clear, actionable failure message wherever the harness binary is expected but absent; the four consumers were enumerated by grep across all four skill directories, not guessed.
  • Risk: Splitting controller_unit_test_src/controller_test.c by hand introduces a copy/paste error (a test moves but its production pairing or its extern/RUN_TEST entry doesn't, or one of the 4 standalone executable()/test() blocks is only partially relocated) and the build fails or silently drops coverage. — Mitigation: Requirement 3 requires the same pass/fail semantics and a distinct harness suite name, so a dropped test surfaces as a suite-count regression rather than a silent gap; design.md's File Structure Plan now names every moving file and every moving meson block explicitly (13 files into harness_unit_test + 4 named standalone binaries + the controller_test.c extern list), rather than referring to "the test files" generically.
  • Risk: meson setup's full-wipe-by-default behavior (build-deployment.md, bug-1565) means a harness build after a controller build in the same dir (or vice versa) silently deletes the other's binaries unless --incremental or /xce-build all is used — pre-existing project behavior, not introduced by this spec, but now applies to one more pair of components. — Mitigation: no design change needed; document as an existing, already-understood constraint (Boundary Commitments/Constraints in design.md).

References

  • .kiro/specs/xce-harness-component/brief.md — original discovery brief with locked-in scope decisions (directory name src/harness/, binary name unchanged, fully decoupled build).
  • .kiro/steering/structure.md — C Daemon Layout Pattern and its documented controller exception.
  • .kiro/steering/build-deployment.md — /xce-build command semantics, meson structure, full-wipe-vs-incremental behavior.
  • .wolf/buglog.json bug-1792 — prior fix to clitest_spawn()'s pty echo handling, confirms the subprocess-spawning nature of test_test_cli.c.