feat(admin-config-tool): task 1.3 - nix and xce-build.sh integration
Add python3 + pycapnp/pyyaml/jsonschema/click/pytest/ruff (+pip) to the nix dev shell, wire a new `xceadmin` case into xce-build.sh (schema sync from src/schema/ and src/configs/schema/, pip install by profile, pytest), package-data config so bundled schemas survive a non-editable install, xce-build SKILL.md docs, and ruff format/check hooks matching this repo's stdin-JSON hook convention (not brief.md's env-var sketch, which would have silently no-op'd). Verified live end-to-end inside `nix develop ./src` for both the default (editable) and release (pinned) profiles; found and fixed 3 real bugs along the way (missing pip, PEP 668 externally-managed- environment, missing package-data for release-profile bundling) — logged as bug-1458/1459/1460. Requirements: 5.5, 8.1, 8.2, 8.6
This commit is contained in:
parent
2e4ef4e209
commit
798f19b83a
7 changed files with 92 additions and 5 deletions
|
|
@ -22,6 +22,16 @@
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/post-write.js\"",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/post-write.js\"",
|
||||||
"timeout": 10
|
"timeout": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "FILE_PATH=$(python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path') or d.get('tool_input',{}).get('path') or '')\" 2>/dev/null); [[ \"$FILE_PATH\" == *.py ]] && ruff format --quiet \"$FILE_PATH\" 2>/dev/null; true",
|
||||||
|
"timeout": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "FILE_PATH=$(python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path') or d.get('tool_input',{}).get('path') or '')\" 2>/dev/null); [[ \"$FILE_PATH\" == *.py ]] && ruff check --quiet \"$FILE_PATH\" 2>/dev/null; true",
|
||||||
|
"timeout": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: xce-build
|
name: xce-build
|
||||||
description: "Build XCE components with tests. Usage: /xce-build [component] [profile] [build_dir]. Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli. Profiles: asan (default), debug (non-ASAN -O0 -g3 for gdb/cores), release."
|
description: "Build XCE components with tests. Usage: /xce-build [component] [profile] [build_dir]. Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli, xceadmin. Profiles: asan (default), debug (non-ASAN -O0 -g3 for gdb/cores), release."
|
||||||
disable-model-invocation: false
|
disable-model-invocation: false
|
||||||
allowed-tools: Bash(*)
|
allowed-tools: Bash(*)
|
||||||
---
|
---
|
||||||
|
|
@ -10,12 +10,13 @@ allowed-tools: Bash(*)
|
||||||
Args: `[component] [profile|build_dir] [build_dir]` — passed as free text after `/xce-build`, e.g. `/xce-build controller debug`. Parse the ARGUMENTS text yourself and run the command below with the parsed values inline.
|
Args: `[component] [profile|build_dir] [build_dir]` — passed as free text after `/xce-build`, e.g. `/xce-build controller debug`. Parse the ARGUMENTS text yourself and run the command below with the parsed values inline.
|
||||||
|
|
||||||
- **No `context: fork`, intentionally.** It was tried (twice) and reproducibly breaks this skill: whenever ARGUMENTS is non-empty (e.g. `controller`), the forked subagent receives no usable task — it responds as if handed only ambient project context, never runs the build, and reports nothing back. Bare `/xce-build` (no args) happens to still "succeed" under `context: fork` only because the empty-arg case is indistinguishable from no task at all failing open into some default — not because forking works. Do not re-add `context: fork` here without first confirming the harness actually threads a non-empty ARGUMENTS payload into the forked subagent's prompt.
|
- **No `context: fork`, intentionally.** It was tried (twice) and reproducibly breaks this skill: whenever ARGUMENTS is non-empty (e.g. `controller`), the forked subagent receives no usable task — it responds as if handed only ambient project context, never runs the build, and reports nothing back. Bare `/xce-build` (no args) happens to still "succeed" under `context: fork` only because the empty-arg case is indistinguishable from no task at all failing open into some default — not because forking works. Do not re-add `context: fork` here without first confirming the harness actually threads a non-empty ARGUMENTS payload into the forked subagent's prompt.
|
||||||
- **component** (1st word, default `all`) — `all`, `lib`, `controller`, `scheduler`, `worker`, `apiserver`, `xcecli`
|
- **component** (1st word, default `all`) — `all`, `lib`, `controller`, `scheduler`, `worker`, `apiserver`, `xcecli`, `xceadmin`
|
||||||
- **profile** (2nd word, default `asan`) — `asan` (debug + AddressSanitizer → `xce.build`), `debug` (non-ASAN `-O0 -g3 -fno-inline -fno-omit-frame-pointer` → `build.dbg`), `release` (`-O3`, no sanitizer → `build.release`). Each profile uses its own default build dir so the variants never clobber each other. If the 2nd word isn't one of these three keywords, treat it as a legacy explicit build_dir instead (3rd word, if present, always overrides the build dir).
|
- **profile** (2nd word, default `asan`) — `asan` (debug + AddressSanitizer → `xce.build`), `debug` (non-ASAN `-O0 -g3 -fno-inline -fno-omit-frame-pointer` → `build.dbg`), `release` (`-O3`, no sanitizer → `build.release`). Each profile uses its own default build dir so the variants never clobber each other. If the 2nd word isn't one of these three keywords, treat it as a legacy explicit build_dir instead (3rd word, if present, always overrides the build dir).
|
||||||
- Use the **`debug`** profile to root-cause heisenbug crashes that ASAN perturbs away and that gdb-on-release turns into a hang/timeout — the non-ASAN `-O0 -g3` build reproduces them and gives a fully symbolized backtrace under gdb (how bug-1282/bug-1286 were cracked).
|
- Use the **`debug`** profile to root-cause heisenbug crashes that ASAN perturbs away and that gdb-on-release turns into a hang/timeout — the non-ASAN `-O0 -g3` build reproduces them and gives a fully symbolized backtrace under gdb (how bug-1282/bug-1286 were cracked).
|
||||||
- **`controller` always builds `xce_test` too** (`meson compile … xce_controller controller_unit_test controller/xce_test`), in every profile — not just `asan`. `xce_test` is the driver `/xce-test-ha` needs; you never have to build it as a separate step after `/xce-build controller`, and the two binaries in a build dir are always the same commit/profile in lockstep.
|
- **`controller` always builds `xce_test` too** (`meson compile … xce_controller controller_unit_test controller/xce_test`), in every profile — not just `asan`. `xce_test` is the driver `/xce-test-ha` needs; you never have to build it as a separate step after `/xce-build controller`, and the two binaries in a build dir are always the same commit/profile in lockstep.
|
||||||
|
- **`xceadmin` is Python, not meson, and ignores the ASAN/debug/release sanitizer semantics entirely** — it is dispatched by a plain shell case branch in `xce-build.sh`, deliberately not wired into `src/meson.build`/`meson.options` (see `.kiro/specs/admin-config-tool/design.md`). The `profile` arg still matters for it, but only to pick the pip install mode: default/`asan`/`debug` → `pip install -e .` (editable, for dev); `release` → `pip install .` (pinned, non-editable, for production). Every invocation — either install mode — syncs the bundled `controller.capnp`/`c.capnp` and the 7 JSON validation schemas from `src/schema/` and `src/configs/schema/` into `xcecommon`'s packaged `schema_files/`, then runs `pytest` against whichever install just happened. Only the `release` run actually exercises the packaged (non-editable) schema resolution path — an editable-only test run can't catch a `package_data` regression there. Requires the nix dev shell (`pycapnp`, `pyyaml`, `jsonschema`, `click`, `pytest`, `ruff` — all provided by `src/nixSupport/xce.nix`).
|
||||||
|
|
||||||
Examples: `/xce-build controller` (ASAN), `/xce-build controller debug` (gdb/core build), `/xce-build all release`.
|
Examples: `/xce-build controller` (ASAN), `/xce-build controller debug` (gdb/core build), `/xce-build all release`, `/xce-build xceadmin` (editable install + tests), `/xce-build xceadmin release` (pinned install + tests).
|
||||||
|
|
||||||
Run (substituting the parsed component/profile/build_dir; omit trailing empty ones):
|
Run (substituting the parsed component/profile/build_dir; omit trailing empty ones):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
- Observable: raising any of these subclasses and catching the common base type recovers the message
|
- Observable: raising any of these subclasses and catching the common base type recovers the message
|
||||||
- _Requirements: 7.1, 7.2, 7.3, 7.4_
|
- _Requirements: 7.1, 7.2, 7.3, 7.4_
|
||||||
|
|
||||||
- [ ] 1.3 Wire xceadmin into the project's build and dev-shell tooling
|
- [x] 1.3 Wire xceadmin into the project's build and dev-shell tooling
|
||||||
- Nix dev shell provides the Python interpreter and all dependencies needed to build and test xceadmin
|
- Nix dev shell provides the Python interpreter and all dependencies needed to build and test xceadmin
|
||||||
- Running the build command for xceadmin performs an editable install by default and a pinned non-editable install under the release profile, running the test suite after whichever install just happened
|
- Running the build command for xceadmin performs an editable install by default and a pinned non-editable install under the release profile, running the test suite after whichever install just happened
|
||||||
- The build step keeps the packaged copies of the wire-protocol schema and the validation schemas in sync with their source locations elsewhere in the repo, and packages both correctly under either install mode
|
- The build step keeps the packaged copies of the wire-protocol schema and the validation schemas in sync with their source locations elsewhere in the repo, and packages both correctly under either install mode
|
||||||
|
|
@ -140,3 +140,9 @@
|
||||||
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 3.4, 6.1, 6.2, 6.3, 7.1, 7.2, 7.3_
|
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 3.4, 6.1, 6.2, 6.3, 7.1, 7.2, 7.3_
|
||||||
- _Boundary: xceadmin.push, xceadmin.discovery_
|
- _Boundary: xceadmin.push, xceadmin.discovery_
|
||||||
- _Depends: 3.3_
|
- _Depends: 3.3_
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- **1.3**: nix's bare `python3` does not put `pip` on PATH — `python3Packages.pip` had to be added to `nativeBuildInputs` alongside the packages design.md's Technology Stack table lists; not a spec gap, just an operational dependency the table didn't need to spell out.
|
||||||
|
- **1.3**: Claude Code `PostToolUse` hooks receive tool input via stdin JSON (`tool_input.file_path`), not a `$FILE_PATH` env var — brief.md's ruff-hook sketch used the env-var form, which would have silently no-op'd. The actual hooks added to `.claude/settings.json` follow this repo's existing `.wolf/hooks/post-write.js` stdin-JSON convention instead. Any future hook work in this repo should follow the same pattern, not brief.md's original sketch.
|
||||||
|
- **1.3**: `src/xceadmin/src/xcecommon/schema_files/` (synced capnp + JSON schema copies) is a build artifact, regenerated every `xce-build.sh xceadmin` run — never hand-edit it, never commit it (`src/xceadmin/.gitignore` excludes it).
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,18 @@ stdenv.mkDerivation {
|
||||||
}))
|
}))
|
||||||
packcc
|
packcc
|
||||||
cacert
|
cacert
|
||||||
|
# Python — for xceadmin (admin config tool, src/xceadmin/). pycapnp links
|
||||||
|
# against the capnproto C++ runtime above; nix's python3Packages.pycapnp
|
||||||
|
# handles that link correctly, unlike a plain `pip install pycapnp`.
|
||||||
|
# See .kiro/specs/admin-config-tool/brief.md "Nix Environment".
|
||||||
|
python3
|
||||||
|
python3Packages.pip
|
||||||
|
python3Packages.pycapnp
|
||||||
|
python3Packages.pyyaml
|
||||||
|
python3Packages.jsonschema
|
||||||
|
python3Packages.click
|
||||||
|
python3Packages.pytest
|
||||||
|
python3Packages.ruff
|
||||||
] ++ (if withDebug then [
|
] ++ (if withDebug then [
|
||||||
(librtti_debug.overrideAttrs (oldattrs: {
|
(librtti_debug.overrideAttrs (oldattrs: {
|
||||||
separateDebugInfo = true;
|
separateDebugInfo = true;
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ cd "$SRC_DIR"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: $0 [component] [profile|build_dir] [build_dir]"
|
echo "Usage: $0 [component] [profile|build_dir] [build_dir]"
|
||||||
echo "Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli"
|
echo "Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli, xceadmin"
|
||||||
echo "Profiles: asan (default), debug (non-ASAN -O0 -g3, for gdb/cores), release"
|
echo "Profiles: asan (default), debug (non-ASAN -O0 -g3, for gdb/cores), release"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
@ -112,6 +112,43 @@ case "$COMPONENT" in
|
||||||
cd frontend/xcecli && CGO_ENABLED=0 go test ./... -count=1
|
cd frontend/xcecli && CGO_ENABLED=0 go test ./... -count=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
xceadmin)
|
||||||
|
# Python — no meson, dispatched by this case statement alone (design
|
||||||
|
# review correction: xceadmin is deliberately NOT wired into
|
||||||
|
# src/meson.build/meson.options, see .kiro/specs/admin-config-tool/
|
||||||
|
# design.md, File Structure Plan). Ignores $BUILD_DIR/$MESON_OPTS;
|
||||||
|
# $PROFILE only selects editable vs. pinned pip install below.
|
||||||
|
XCEADMIN_DIR="$SRC_DIR/xceadmin"
|
||||||
|
SCHEMA_DST="$XCEADMIN_DIR/src/xcecommon/schema_files"
|
||||||
|
echo "=== Syncing xceadmin bundled schemas ==="
|
||||||
|
mkdir -p "$SCHEMA_DST/json"
|
||||||
|
cp "$SRC_DIR/schema/controller.capnp" "$SCHEMA_DST/controller.capnp"
|
||||||
|
cp "$SRC_DIR/schema/c.capnp" "$SCHEMA_DST/c.capnp"
|
||||||
|
cp "$SRC_DIR/configs/schema/"*.schema.json "$SCHEMA_DST/json/"
|
||||||
|
cd "$XCEADMIN_DIR"
|
||||||
|
# nix's python3 site-packages lives under the read-only /nix/store,
|
||||||
|
# so `pip install` (editable or pinned) can't target it directly —
|
||||||
|
# it refuses with "externally-managed-environment" (PEP 668). Use a
|
||||||
|
# throwaway venv that inherits the nix-provided pycapnp/pyyaml/
|
||||||
|
# jsonschema/click/pytest (--system-site-packages) so pip only ever
|
||||||
|
# has to install xceadmin itself, never rebuild those from PyPI.
|
||||||
|
# Recreated on every invocation for a clean, reproducible install —
|
||||||
|
# same philosophy as setup_build's `rm -rf "$BUILD_DIR"` above.
|
||||||
|
VENV_DIR="$XCEADMIN_DIR/.venv"
|
||||||
|
rm -rf "$VENV_DIR"
|
||||||
|
python3 -m venv --system-site-packages "$VENV_DIR"
|
||||||
|
if [ "$PROFILE" = "release" ]; then
|
||||||
|
echo "=== Building xceadmin Python package (pinned, non-editable) ==="
|
||||||
|
"$VENV_DIR/bin/pip" install . --quiet
|
||||||
|
else
|
||||||
|
echo "=== Building xceadmin Python package (editable) ==="
|
||||||
|
"$VENV_DIR/bin/pip" install -e . --quiet
|
||||||
|
fi
|
||||||
|
echo "=== Running xceadmin tests ==="
|
||||||
|
"$VENV_DIR/bin/python" -m pytest tests/ -v
|
||||||
|
echo "=== Done: xceadmin (profile=$PROFILE) ==="
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
all)
|
all)
|
||||||
setup_build
|
setup_build
|
||||||
echo "=== Building all components ==="
|
echo "=== Building all components ==="
|
||||||
|
|
|
||||||
12
src/xceadmin/.gitignore
vendored
Normal file
12
src/xceadmin/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
.venv/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# Synced package data — regenerated from src/schema/ and src/configs/schema/
|
||||||
|
# by `xce-build.sh xceadmin`'s schema-sync step; never hand-edited, never committed.
|
||||||
|
src/xcecommon/schema_files/
|
||||||
|
|
@ -27,3 +27,12 @@ include = ["xceadmin*", "xcecommon*"]
|
||||||
|
|
||||||
[tool.setuptools.package-dir]
|
[tool.setuptools.package-dir]
|
||||||
"" = "src"
|
"" = "src"
|
||||||
|
|
||||||
|
# Bundled copies of controller.capnp/c.capnp and the 7 JSON validation
|
||||||
|
# schemas (synced from src/schema/ and src/configs/schema/ by
|
||||||
|
# src/tools/xce-build.sh's `xceadmin)` case, see Requirement 5.5). Without
|
||||||
|
# this, a non-editable `pip install .` builds a wheel containing only *.py
|
||||||
|
# files and silently drops schema_files/ — an editable install would never
|
||||||
|
# catch this since it resolves straight into the checked-out source tree.
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
xcecommon = ["schema_files/*.capnp", "schema_files/json/*.schema.json"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue