Files
ironclaw/scripts/build-wasm-extensions.sh
Josh Ford 318a6e6748 docs: consolidate docs/reborn/ into docs/internal/reborn/ (#7559)
* docs: consolidate docs/reborn/ into docs/internal/reborn/

Move-only migration; no content changes beyond path references. Executes
the follow-up that PR #7259 left open: docs/.mintignore's reborn/ entry
was kept only because the path was load-bearing, and its comment
documented that it moves under internal/ once its consumers move with it.

- git mv docs/reborn docs/internal/reborn (115 files, history preserved)
- rewrite docs/reborn -> docs/internal/reborn across every consumer
  (crate AGENTS/READMEs and doc-comments, .claude/ skills and rules,
  AGENTS.md, CI scripts, reborn-e2e.yml path filters, Dockerfile, tests,
  docs/internal plans)
- fix six relative internal/adr/ links inside the moved tree for the
  added directory level
- drop reborn/ from docs/.mintignore and FROZEN_MINTIGNORE_PATTERNS in
  scripts/ci/docs_publication_boundary.py (the frozen list only ever
  shrinks); internal/ already fences the new location

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: classify tests/dockerfile_runtime_home.rs and shrink boundary self-test fixture

Two CI gates failed on the docs/reborn consolidation and forced decisions
this commit records:

- The Reborn PR test planner failed closed on tests/dockerfile_runtime_home.rs
  (its path-rewrite edit is functional: the test reads the moved deploy doc).
  The file was deliberately unmapped because no lane inventoried it. Decide it
  now: _root_test_partitions() and run-reborn-root-partition.sh both inventory
  it alongside support_unit_tests.rs, so the hermetic root-partition lanes run
  it (they previously ran it nowhere) and a change to it selects its partition.
  With the reader laned, map the two config.hosted-single-tenant*.toml readers
  it owns in DOCKER_RUNTIME_CONFIG_OWNERS — root-test owners select their root
  partition, completing the per-file decision set the planner comments left
  open. docker/process-sandbox-entrypoint.sh stays fail-closed.
- test_docs_publication_boundary.py's subset fixture still listed reborn/ in
  the frozen mintignore list; use the surviving entries.

Verified: both self-test suites pass (77 planner + boundary), the planner
emits a valid selected plan for this PR's full 342-path diff, shell and
Python inventories agree on partition assignment (index 0), and
dockerfile_runtime_home passes (19 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 09:39:06 +00:00

279 lines
9.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build WASM tools and channels that still have local source entries.
#
# Verifies local-source registry entries compile against the current WIT
# definitions. Registry entries that only publish release artifacts are skipped.
#
# Prerequisites:
# rustup target add wasm32-wasip2
# rustup target add wasm32-wasip1
# cargo install cargo-component --locked
#
# Usage:
# ./scripts/build-wasm-extensions.sh # build all
# ./scripts/build-wasm-extensions.sh --tools # tools only
# ./scripts/build-wasm-extensions.sh --channels # channels only
# ./scripts/build-wasm-extensions.sh --first-party # first-party extensions only
#
# The package root is resolved from the crate inventory
# (scripts/ci/lib/crate_tree.py) by crate NAME, not from a literal path, so the
# target-architecture family move (crates/<family>/ironclaw_*, PROPOSAL §5)
# does not leave this script unable to find the manifests it is supposed to
# build (docs/internal/reborn/target-architecture/CHECKLIST.md WS10, #6963).
#
# WS2 moved the packages out of the support crate: they were
# `<support-crate>/assets/<ext>/`, they are now `packages/<ext>/` SIBLING to
# the support crate (PROPOSAL §5 — a package directory is self-contained and
# is not owned by any crate). So discovery still anchors on the crate name and
# then steps to its sibling, which keeps both properties: name-keyed (survives
# a family move) and following the code (survives this one).
#
# Override for tests: FIRST_PARTY_ASSETS_ROOT bypasses discovery.
set -euo pipefail
shopt -s nullglob
cd "$(dirname "$0")/.."
FIRST_PARTY_CRATE="${FIRST_PARTY_CRATE:-ironclaw_extension_support}"
# Resolve the crate that owns the first-party extension assets, at any depth.
# Failing here is deliberate: "cannot find the crate" must be an actionable
# repoint, not an empty build that looks like there was nothing to do.
resolve_first_party_assets_root() {
if [ -n "${FIRST_PARTY_ASSETS_ROOT:-}" ]; then
printf '%s\n' "${FIRST_PARTY_ASSETS_ROOT}"
return 0
fi
local inventory match count discovery_error
# stdout and stderr are captured separately on purpose: merging them with
# `2>&1` would fold any Python warning into the inventory itself and turn a
# benign diagnostic into a bogus crate directory.
discovery_error="$(mktemp)"
if ! inventory=$(python3 scripts/ci/lib/crate_tree.py . 2>"${discovery_error}"); then
echo " FAIL first-party discovery (crate discovery failed: $(cat "${discovery_error}"))" >&2
rm -f "${discovery_error}"
return 1
fi
rm -f "${discovery_error}"
match=$(printf '%s\n' "${inventory}" | awk -F/ -v want="${FIRST_PARTY_CRATE}" '$NF == want')
count=$(printf '%s' "${match}" | grep -c . || true)
if [ "${count}" -ne 1 ]; then
echo " FAIL first-party discovery (expected exactly one crate named \
'${FIRST_PARTY_CRATE}', found ${count} — repoint FIRST_PARTY_CRATE in the same PR that \
renamed or moved it)" >&2
return 1
fi
# `packages/` sits beside the support crate, not inside it.
printf '%s/packages\n' "$(dirname "${match}")"
}
# cargo-component may be installed under ~/.cargo while Homebrew rustc wins PATH on
# developer machines. Force rustup rustc when available so installed WASI targets are
# visible to component builds.
RUSTUP_RUSTC=""
RUSTUP_TOOLCHAIN_NAME=""
if command -v rustup >/dev/null 2>&1; then
RUSTUP_RUSTC=$(rustup which rustc 2>/dev/null || true)
RUSTUP_TOOLCHAIN_NAME=$(rustup show active-toolchain 2>/dev/null | awk '{print $1}' || true)
if [ -z "${RUSTC:-}" ] && [ -n "$RUSTUP_RUSTC" ]; then
export RUSTC="$RUSTUP_RUSTC"
fi
fi
cargo_component_build() {
if [ -n "$RUSTUP_TOOLCHAIN_NAME" ]; then
RUSTC="${RUSTC:-$RUSTUP_RUSTC}" rustup run "$RUSTUP_TOOLCHAIN_NAME" cargo component build "$@"
else
cargo component build "$@"
fi
}
BUILD_TOOLS=true
BUILD_CHANNELS=true
BUILD_FIRST_PARTY=true
FAILED=()
if [[ "${1:-}" == "--tools" ]]; then
BUILD_CHANNELS=false
BUILD_FIRST_PARTY=false
elif [[ "${1:-}" == "--channels" ]]; then
BUILD_TOOLS=false
BUILD_FIRST_PARTY=false
elif [[ "${1:-}" == "--first-party" ]]; then
BUILD_TOOLS=false
BUILD_CHANNELS=false
fi
fail_build() {
local name="$1"
local reason="$2"
echo " FAIL $name ($reason)"
FAILED+=("$name")
}
build_manifest_set() {
local label="$1"
local builder="$2"
shift 2
echo "Building $label..."
if [ "$#" -eq 0 ]; then
fail_build "$label" "no manifests found"
return 0
fi
local manifest
for manifest in "$@"; do
"$builder" "$manifest"
done
}
build_extension() {
local manifest_path="$1"
local hidden
local source_dir
local crate_name
if ! hidden=$(jq -r '.hidden // false' "$manifest_path"); then
fail_build "$(basename "$manifest_path" .json)" "could not read hidden flag"
return 0
fi
if [ "$hidden" = "true" ]; then
echo " SKIP $(basename "$manifest_path" .json) (hidden registry entry)"
return 0
fi
if ! source_dir=$(jq -r '.source.dir // empty' "$manifest_path"); then
fail_build "$(basename "$manifest_path" .json)" "could not read source dir"
return 0
fi
if ! crate_name=$(jq -r '.source.crate_name // empty' "$manifest_path"); then
fail_build "$(basename "$manifest_path" .json)" "could not read crate name"
return 0
fi
local name
name=$(basename "$manifest_path" .json)
if [ -z "$source_dir" ]; then
echo " SKIP $name (registry entry has no local source)"
return 0
fi
if [ -z "$crate_name" ]; then
echo " SKIP $name (registry entry has no local crate name)"
return 0
fi
if [ ! -d "$source_dir" ]; then
echo " SKIP $name (source dir $source_dir not found)"
return 0
fi
echo " BUILD $name ($crate_name) from $source_dir"
if ! cargo_component_build --release --target wasm32-wasip2 --manifest-path "$source_dir/Cargo.toml" 2>&1; then
fail_build "$name" "cargo component build failed"
return 0
fi
echo " OK $name"
}
build_first_party_extension() {
local manifest_path="$1"
local extension_root
local source_dir
local module_path
local runtime_kind
local target_root
local artifact
local candidate
local name
extension_root=$(dirname "$manifest_path")
source_dir="$extension_root/wasm-src"
name="first-party/$(basename "$extension_root")"
if ! runtime_kind=$(awk -F'"' '/^[[:space:]]*kind[[:space:]]*=/{ print $2; exit }' "$manifest_path"); then
fail_build "$name" "could not read manifest"
return 0
fi
if [ "$runtime_kind" != "wasm" ]; then
echo " SKIP $name (runtime kind $runtime_kind is host-native)"
return 0
fi
if ! module_path=$(awk -F'"' '/^[[:space:]]*module[[:space:]]*=/{ print $2; exit }' "$manifest_path"); then
fail_build "$name" "could not read manifest"
return 0
fi
if [ ! -d "$source_dir" ]; then
fail_build "$name" "source dir $source_dir not found"
return 0
fi
if [ -z "$module_path" ]; then
fail_build "$name" "manifest missing runtime module"
return 0
fi
echo " BUILD $name from $source_dir"
if ! cargo_component_build --release --target wasm32-wasip2 --manifest-path "$source_dir/Cargo.toml" 2>&1; then
fail_build "$name" "cargo component build failed"
return 0
fi
target_root="${CARGO_TARGET_DIR:-$source_dir/target}"
artifact=""
for target_triple in wasm32-wasip2 wasm32-wasip1 wasm32-wasi; do
candidate="$target_root/$target_triple/release/$(basename "$module_path")"
if [ -f "$candidate" ]; then
artifact="$candidate"
break
fi
done
if [ ! -f "$artifact" ]; then
fail_build "$name" "expected artifact $(basename "$module_path") not found under $target_root"
return 0
fi
if ! mkdir -p "$(dirname "$extension_root/$module_path")"; then
fail_build "$name" "could not create output directory"
return 0
fi
if ! cp "$artifact" "$extension_root/$module_path"; then
fail_build "$name" "could not copy artifact"
return 0
fi
echo " OK $name"
}
# `${arr[@]+"${arr[@]}"}` rather than `"${arr[@]}"`: under `set -u`, bash before
# 4.4 (macOS ships 3.2) treats an empty array expansion as an unbound variable
# and dies with a shell diagnostic instead of reaching build_manifest_set's
# "no manifests found" guard. Same outcome either way — non-zero — but only this
# form reports WHICH set was empty, which is the actionable half.
if $BUILD_TOOLS; then
tool_manifests=(registry/tools/*.json)
build_manifest_set "WASM tools" build_extension ${tool_manifests[@]+"${tool_manifests[@]}"}
fi
if $BUILD_FIRST_PARTY; then
if first_party_assets=$(resolve_first_party_assets_root); then
first_party_manifests=("${first_party_assets}"/*/manifest.toml)
else
first_party_manifests=()
fi
build_manifest_set "first-party WASM extensions" build_first_party_extension \
${first_party_manifests[@]+"${first_party_manifests[@]}"}
fi
if $BUILD_CHANNELS; then
channel_manifests=(registry/channels/*.json)
build_manifest_set "WASM channels" build_extension ${channel_manifests[@]+"${channel_manifests[@]}"}
fi
echo ""
if [ ${#FAILED[@]} -gt 0 ]; then
echo "FAILED: ${FAILED[*]}"
exit 1
else
echo "All WASM extensions built successfully."
fi