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
17 KiB
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.his 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-designreview: only 13 of the 17 xce_test-related test files actually live inside the singlecontroller_unit_testbinary (test_waldump.cthroughtest_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 insrc/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 andexecthe actual builtxce_testbinary over a pty (clitest_spawn/resolve_xce_test_path), not white-box linkage. The newharness_unit_testbinary (the one genuinely new binary — the other 4 relocate verbatim) therefore has a genuine build-order dependency on thexce_testexecutable target — a patterncontroller_unit_testnever 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 oneextern/RUN_TESTpair per test function (lines 68-80 for the 13 moving functions;test_ctrlstatusat line 64 stays, since it tests controller's ownctrlstatus.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'scontroller)case compilesxce_controller controller_unit_test controller/xce_test—controller/xce_testis the one target to drop, and a newharness)case follows the identical pattern already used byscheduler)/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.
- 14 of xce_test's 15 source files have zero dependency on controller-private headers or sources; only
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 15xce_test_srcfiles (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 generatedcontroller.capnp.hschema header, xce_test's own component-local headers (14 of the 15, one-to-one with the.cfiles, plusxcetest_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.hrequires 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"acrosssrc/controller/*.candsrc/controller/testsuite/*.c; read the function definition. - Findings:
ctrlstatus_fmt.hdeclares exactly one function,ctrlstatus_format_time_ms(), asstatic inline(ctrlstatus_fmt.h:33) — header-only, no.cimplementation, no link-time dependency. Four consumers:test_ctrlstatus.c(controller's own white-box test — stays with controller), andjoblist_display.c/test.c/test_joblist_display.c(all three move to harness). No production controller.cfile 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_srcbut have no matching production.ccounterpart (unlike the other 14 pairs) — needed to understand how they test xce_test iftest.citself isn't recompiled intocontroller_unit_test. - Sources Consulted: Read
test_test_cli.c(clitest_spawn/clitest_spawn_ex/resolve_xce_test_path) and grep forresolve_xce_test_pathacross the testsuite. - Findings:
test_test_cli.cdefinesresolve_xce_test_path()andclitest_spawn()/clitest_spawn_ex()itself — these locate the already-builtxce_testbinary on disk andfork/execit 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.creusesresolve_xce_test_pathvia anexterndeclaration into the samecontroller_unit_testbinary (both are linked into one executable today, soexternresolves 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_testexecutable before running these two tests — a real build-order dependency to declare explicitly insrc/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-hadeploys already-built binaries and soft-skips missing ones perbuild-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.mddocs are the concrete edit surface for Requirement 5;xce-deploy-haneeds 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
controllerandxce_testbuilds to plan the decoupling precisely rather than guessing at meson internals. - Sources Consulted: Read
src/tools/xce-build.sh:282-340(thecase "$COMPONENT" in ... esacdispatch). - Findings:
controller)(lines 292-299) runsmeson compile ... xce_controller controller_unit_test controller/xce_testthenmeson test --suite controller. Sibling cases (scheduler),worker)) follow an identical three-part pattern:setup_build, an explicitmeson compiletarget list, then a conditionalmeson test --suite <name>.all)(not shown here, documented inbuild-deployment.md) compiles with no explicit target list, so it will automatically pick up a newharnesstarget oncesrc/meson.buildgainssubdir('harness')— noall)case change needed. - Implications: The fix is mechanical and low-risk: drop
controller/xce_testfrom line 295, and add a newharness)case built from the same three-part template already proven byscheduler)/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 undersrc/<component>/src/, but documentscontroller/as an explicit exception that keeps.cfiles flat in the component root while still having its owntestsuite/. - 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/*.hfor production sources (the 15 files),src/harness/testsuite/*.cfor 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 tosrc/controller/include/(controller's real component-header dir) and have harness depend on that explicitly as an allowed exception; (3) promote it to baresrc/include/(the libxce public-header boundary); (4) promote it tosrc/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'stest_ctrlstatus.cto include it asxce/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-designcritical issue (baresrc/include/reads as libxce's real runtime API surface, which this isn't) — resolved to the existingxce/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-designreview found the original design's "one newharness_unit_testbinary" model was factually incomplete — 4 of the 17 test files are already independent binaries insrc/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_testbinary; (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.candtest_xcetest_process_entry.cspawn the builtxce_testbinary as a subprocess; they will silently exec a stale or missing binary if the harness's own build doesn't guaranteexce_testis built first. - Alternatives Considered: (1) leave build ordering implicit (risk: flaky/stale-binary test runs); (2) declare the dependency explicitly in
src/harness/meson.buildso 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.cby hand introduces a copy/paste error (a test moves but its production pairing or itsextern/RUN_TESTentry doesn't, or one of the 4 standaloneexecutable()/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 distinctharnesssuite 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 intoharness_unit_test+ 4 named standalone binaries + thecontroller_test.cextern list), rather than referring to "the test files" generically. - Risk:
meson setup's full-wipe-by-default behavior (build-deployment.md, bug-1565) means aharnessbuild after acontrollerbuild in the same dir (or vice versa) silently deletes the other's binaries unless--incrementalor/xce-build allis 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 indesign.md).
References
.kiro/specs/xce-harness-component/brief.md— original discovery brief with locked-in scope decisions (directory namesrc/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-buildcommand semantics, meson structure, full-wipe-vs-incremental behavior..wolf/buglog.jsonbug-1792 — prior fix toclitest_spawn()'s pty echo handling, confirms the subprocess-spawning nature oftest_test_cli.c.