#!/usr/bin/env bash # Public SDK isolation and sibling-library ratchets use the fixed composition map # when supplied; a manual invocation uses sources prepared by the checkout owner. set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) fail=0 source_path() { python3 "$root/scripts/component-source.py" "$1"; } module_path() { local mod mod=$(awk '/^module /{print $2; exit}' "$1/go.mod") [ -n "$mod" ] || { echo "boundary: source lacks a module identity: $1" >&2; return 1; } printf '%s\n' "$mod" } backend=$(source_path Agent-System-Backend) intent=$(source_path Agent-Intent) native=$(source_path Agent-Native) squad=$(source_path Agent-Squad) backend_mod=$(module_path "$backend") intent_mod=$(module_path "$intent") native_mod=$(module_path "$native") squad_mod=$(module_path "$squad") check() { local path="$1" if [ -d "$path" ] && rg -n --glob '*.go' --glob '*.ts' --glob '*.tsx' -F \ -e "$backend_mod/internal" -e 'agent-system-backend/internal' "$path" >/dev/null; then echo "boundary: $path imports backend internal" >&2 rg -n --glob '*.go' --glob '*.ts' --glob '*.tsx' -F \ -e "$backend_mod/internal" -e 'agent-system-backend/internal' "$path" >&2 fail=1 fi } sdk="$backend/pkg/extension" if [ -d "$sdk" ]; then hits=$(rg -n --glob '*.go' --glob '!link/**' -F -e "$backend_mod/internal" \ -e 'agent-system-backend/internal' "$sdk" | { grep -v '/link/' || true; } || true) if [ -n "$hits" ]; then echo "boundary: pkg/extension imports backend internal" >&2 echo "$hits" >&2 fail=1 fi fi check "$root/examples" # Preserve the allowlist's semantic package names when public module prefixes # change. No baseline is regenerated during dependency migration. normalize() { local value="$1" idx for idx in 0 1 2; do case "$value" in "${canonical[$idx]}") printf '%s' "${legacy[$idx]}"; return ;; "${canonical[$idx]}"/*) printf '%s%s' "${legacy[$idx]}" "${value#"${canonical[$idx]}"}"; return ;; esac done printf '%s' "$value" } canonical=("$intent_mod" "$native_mod" "$squad_mod") legacy=(agent-intent agent-native agent-squad) siblings=("${canonical[@]}" "${legacy[@]}") allow="$root/policy/lib-deps-allowlist.txt" observed=$(mktemp) recorded=$(mktemp) imports=$(mktemp) trap 'rm -f "$observed" "$recorded" "$imports"' EXIT for dir in "$intent" "$native" "$squad"; do mod=$(module_path "$dir") (cd "$root/scripts" && GOWORK=off go run lint-imports.go "$dir") > "$imports" while read -r importer rest; do for imp in $rest; do for sib in "${siblings[@]}"; do case "$imp" in "$sib"|"$sib"/*) ;; *) continue ;; esac case "$importer" in "$mod"|"$mod"/*) ;; *) continue ;; esac if [ "$(normalize "$sib")" != "$(normalize "$mod")" ]; then printf '%s %s\n' "$(normalize "$importer")" "$(normalize "$imp")" fi done done done < "$imports" done | sort -u > "$observed" grep -v -e '^#' -e '^$' "$allow" | sort -u > "$recorded" || true new_pairs=$(comm -13 "$recorded" "$observed" || true) if [ -n "$new_pairs" ]; then echo "boundary: new sibling-library import pairs" >&2 echo "$new_pairs" >&2 fail=1 fi stale_pairs=$(comm -23 "$recorded" "$observed" || true) if [ -n "$stale_pairs" ]; then echo "warn: sibling-library pairs no longer imported; delete from policy/lib-deps-allowlist.txt" >&2 echo "$stale_pairs" >&2 fi for dir in "$intent" "$native" "$squad"; do mod=$(module_path "$dir") deps_out=$(cd "$dir" && GOWORK=off go list -deps ./...) || fail=1 for sib in "${siblings[@]}"; do [ "$(normalize "$sib")" = "$(normalize "$mod")" ] && continue while IFS= read -r dep; do case "$dep" in "$sib"|"$sib"/*) echo "boundary: $mod core depends on $sib" >&2; fail=1 ;; esac done <<< "$deps_out" done done while IFS= read -r file; do case "$file" in "$squad"/adapters/intent/*|"$backend"/internal/intentgate/*|"$backend"/internal/intentsem/*) ;; *) echo "boundary: $file imports agent-intent" >&2; fail=1 ;; esac done < <(rg -l --glob '*.go' --glob '!**/deps/**' -F -e "\"$intent_mod\"" \ -e "\"$intent_mod/" -e '"agent-intent"' -e '"agent-intent/' "$native" "$squad" "$backend" || true) [ "$fail" -eq 0 ] || exit 1 bash "$root/scripts/lint-architecture.sh" echo "boundaries ok"