#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/fe-ai-tools-fleet.XXXXXX")"
APP_DIR="${TMP_DIR}/corva-dc-fe-example"
CLI=(node "${ROOT_DIR}/scripts/fleet/cli.mjs")
PACKAGE_VERSION="$(node -p "require('${ROOT_DIR}/package.json').version")"
trap 'rm -rf "${TMP_DIR}"' EXIT

fail_if_passes() {
  local message="$1"
  shift
  if "$@" > /dev/null 2>&1; then
    echo "fleet test: ${message}" >&2
    exit 1
  fi
}

assert_protocol() {
  local file="$1"
  local expected_kind="$2"
  node --input-type=module - "${file}" "${expected_kind}" <<'NODE'
import { readFileSync } from 'node:fs';
const [path, expectedKind] = process.argv.slice(2);
const value = JSON.parse(readFileSync(path, 'utf8'));
if (value.schemaVersion !== 1 || value.kind !== expectedKind) process.exit(1);
if (value.issues !== undefined) {
  if (!Array.isArray(value.issues)) process.exit(1);
  for (const issue of value.issues) {
    if (!issue || !/^[A-Z][A-Z0-9_]+$/.test(issue.code) || typeof issue.message !== 'string') process.exit(1);
  }
}
NODE
}

index_snapshot() {
  node --input-type=module - "$1/.git/index" <<'NODE'
import { createHash } from 'node:crypto';
import { readFileSync, statSync } from 'node:fs';
const path = process.argv[2];
const stat = statSync(path);
process.stdout.write(`${stat.mtimeMs}:${createHash('sha256').update(readFileSync(path)).digest('hex')}`);
NODE
}

mkdir -p "${APP_DIR}/.corva" "${APP_DIR}/src/ai-tools"

cat > "${APP_DIR}/manifest.json" <<'JSON'
{
  "application": { "key": "corva.example.ui" }
}
JSON

cat > "${APP_DIR}/src/ai-tools/README.md" <<'MD'
# Ask Corva tools

Covered under canon rev `8`
MD

cat > "${APP_DIR}/package.json" <<'JSON'
{
  "name": "example-app",
  "dependencies": { "@corva/fe-ai-tools": "^1.4.0" },
  "scripts": { "ai-tools:verify": "node -e \"process.exit(0)\"" }
}
JSON
cat > "${APP_DIR}/yarn.lock" <<'LOCK'
"@corva/fe-ai-tools@^1.4.0":
  version "1.4.0"
  resolved "https://registry.example.invalid/@corva/fe-ai-tools-1.4.0.tgz"
LOCK

INIT_ARGS=(
  manifest init
  --root "${APP_DIR}"
  --repository corva-ai/corva-dc-fe-example
  --app-key corva.example.ui
  --profile dc-app
  --tool-root src/ai-tools
  --contract-revision 2
  --canon-revision 8
  --verification-command "yarn ai-tools:verify"
)

"${CLI[@]}" "${INIT_ARGS[@]}" > "${TMP_DIR}/dry-run.json" 2> "${TMP_DIR}/dry-run.log"
test ! -e "${APP_DIR}/.corva/ai-tools.json"
grep -q 'dry-run: would create' "${TMP_DIR}/dry-run.log"
grep -q "https://unpkg.com/@corva/fe-ai-tools@${PACKAGE_VERSION}/fleet/app-manifest.schema.json" "${TMP_DIR}/dry-run.json"

"${CLI[@]}" "${INIT_ARGS[@]}" --write > /dev/null
fail_if_passes 'manifest init overwrote an existing manifest' "${CLI[@]}" "${INIT_ARGS[@]}" --write

V2_DIR="${TMP_DIR}/manifest-v2"
mkdir -p "${V2_DIR}/.corva" "${V2_DIR}/src/ai-tools" "${V2_DIR}/ai-tests/runs/fixture"
cp "${APP_DIR}/manifest.json" "${APP_DIR}/package.json" "${APP_DIR}/yarn.lock" "${V2_DIR}/"
cp "${APP_DIR}/src/ai-tools/README.md" "${V2_DIR}/src/ai-tools/README.md"
printf '%s\n' '| * | deadbeef | integration | agent:x/run:1 | pass |' > "${V2_DIR}/delivery-log.md"
printf '%s\n' '# Live report' > "${V2_DIR}/ai-tests/runs/fixture/report.md"
cat > "${TMP_DIR}/coverage.json" <<'JSON'
{
  "status": "full",
  "tools": ["get-current-state"],
  "capabilities": ["entity-scope"],
  "applicability": { "loadedData": "no", "goals": "no" },
  "evidence": { "liveReport": "ai-tests/runs/fixture/report.md" }
}
JSON
"${CLI[@]}" manifest init --root "${V2_DIR}" --repository corva-ai/corva-dc-fe-example \
  --app-key corva.example.ui --profile dc-app --tool-root src/ai-tools \
  --contract-revision 2 --canon-revision 8 --verification-command "yarn ai-tools:verify" \
  --coverage-file "${TMP_DIR}/coverage.json" --write >/dev/null
grep -q '"schemaVersion": 2' "${V2_DIR}/.corva/ai-tools.json"
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/app-manifest-v2.schema.json" "${V2_DIR}/.corva/ai-tools.json" >/dev/null

cat > "${TMP_DIR}/coverage-invalid.json" <<'JSON'
{
  "status": "full",
  "tools": [],
  "capabilities": [],
  "applicability": { "loadedData": "no", "goals": "yes" },
  "evidence": {}
}
JSON
INVALID_V2_DIR="${TMP_DIR}/manifest-v2-invalid"
mkdir -p "${INVALID_V2_DIR}/.corva"
fail_if_passes 'manifest v2 accepted empty full coverage and goals without loaded data' \
  "${CLI[@]}" manifest init --root "${INVALID_V2_DIR}" --repository corva-ai/corva-dc-fe-example \
  --app-key corva.example.ui --profile dc-app --tool-root src/ai-tools \
  --contract-revision 2 --canon-revision 8 --verification-command "yarn ai-tools:verify" \
  --coverage-file "${TMP_DIR}/coverage-invalid.json"

cat > "${TMP_DIR}/coverage-missing-tools.json" <<'JSON'
{
  "status": "full",
  "capabilities": [],
  "applicability": { "loadedData": "no", "goals": "no" },
  "evidence": {}
}
JSON
if "${CLI[@]}" manifest init --root "${INVALID_V2_DIR}" --repository corva-ai/corva-dc-fe-example \
  --app-key corva.example.ui --profile dc-app --tool-root src/ai-tools \
  --contract-revision 2 --canon-revision 8 --verification-command "yarn ai-tools:verify" \
  --coverage-file "${TMP_DIR}/coverage-missing-tools.json" > /dev/null 2> "${TMP_DIR}/coverage-missing-tools.err"; then
  echo 'fleet test: manifest v2 accepted missing coverage.tools' >&2
  exit 1
fi
grep -q 'COVERAGE_TOOLS_INVALID' "${TMP_DIR}/coverage-missing-tools.err"

git -C "${APP_DIR}" init -q
git -C "${APP_DIR}" config user.email fleet-test@corva.ai
git -C "${APP_DIR}" config user.name 'Fleet test'
git -C "${APP_DIR}" remote add origin git@github.com:corva-ai/corva-dc-fe-example.git
git -C "${APP_DIR}" add .
git -C "${APP_DIR}" commit -qm fixture
APP_HEAD="$(git -C "${APP_DIR}" rev-parse HEAD)"

cat > "${TMP_DIR}/plan-registry.json" <<'JSON'
{
  "schemaVersion": 1,
  "repositories": [
    { "repository": "corva-ai/corva-dc-fe-example", "profile": "dc-app", "rolloutGroup": "canary" }
  ]
}
JSON

"${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/doctor.json"
assert_protocol "${TMP_DIR}/doctor.json" doctor-result
grep -q '"status": "READY"' "${TMP_DIR}/doctor.json"
test -z "$(git -C "${APP_DIR}" status --porcelain=v1 --untracked-files=all)"

# A workspace app keeps its manifest, kit dependency and verify script beside the app, while the
# lockfile and the package-manager declaration stay at the repository root. The cases below fix
# which root each fact is read from, and refuse by issue code rather than by exit status alone.
WS_DIR="${TMP_DIR}/workspace"
mkdir -p "${WS_DIR}/apps"
cp -R "${APP_DIR}" "${WS_DIR}/apps/example"
rm -rf "${WS_DIR}/apps/example/.git"
mv "${WS_DIR}/apps/example/yarn.lock" "${WS_DIR}/yarn.lock"
write_workspace_root() {
  cat > "${WS_DIR}/package.json" <<JSON
{ "private": true, "workspaces": ["apps/*"]${1:+, ${1}} }
JSON
}
write_workspace_root

# $1 names what the case proves, $2 the issue code the refusal must carry, rest is the command.
# An exit status alone would also be produced by an unrelated failure, and would let the diagnostic
# say anything at all.
ws_refuses() {
  local message="$1" code="$2"
  shift 2
  if "$@" --json > "${TMP_DIR}/ws-refusal.json"; then
    echo "fleet test: ${message}" >&2
    exit 1
  fi
  if ! grep -q "\"${code}\"" "${TMP_DIR}/ws-refusal.json"; then
    echo "fleet test: ${message} - refused without ${code}" >&2
    cat "${TMP_DIR}/ws-refusal.json" >&2
    exit 1
  fi
}

"${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}" --json \
  > "${TMP_DIR}/workspace-doctor.json"
assert_protocol "${TMP_DIR}/workspace-doctor.json" doctor-result
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/workspace-doctor.json" > /dev/null
grep -q '"status": "READY"' "${TMP_DIR}/workspace-doctor.json"
grep -q '"workspaceRoot"' "${TMP_DIR}/workspace-doctor.json"

# Without the split the same app is unverifiable, which is what makes the flag load-bearing rather
# than cosmetic: the lockfile it needs is one directory up.
ws_refuses 'doctor read a workspace app without its workspace root' LOCKFILE_MISSING \
  "${CLI[@]}" doctor --root "${WS_DIR}/apps/example"

# The workspace root is not itself an app: pointing both roots at it must still find no manifest.
ws_refuses 'doctor accepted the workspace root as an app' MANIFEST_MISSING \
  "${CLI[@]}" doctor --root "${WS_DIR}" --workspace-root "${WS_DIR}"

# An app root outside the workspace it claims would let a passing app vouch for an unrelated tree.
ws_refuses 'doctor accepted an app root outside its workspace root' WORKSPACE_ROOT_ESCAPE \
  "${CLI[@]}" doctor --root "${APP_DIR}" --workspace-root "${WS_DIR}"

# Containment is not membership: an ancestor that never claims the app could otherwise stand in for
# the real root and supply whichever lockfile answered best.
# The foreign root is complete - package.json, lockfile, workspace globs - and differs from the
# real one in one respect only: its globs do not match where this app sits.
mkdir -p "${TMP_DIR}/foreign/vendor"
cp -R "${WS_DIR}/apps/example" "${TMP_DIR}/foreign/vendor/example"
cp "${WS_DIR}/yarn.lock" "${TMP_DIR}/foreign/yarn.lock"
cat > "${TMP_DIR}/foreign/package.json" <<'JSON'
{ "private": true, "workspaces": ["apps/*"] }
JSON
ws_refuses 'doctor accepted a workspace root that does not claim the app' WORKSPACE_MEMBER_UNDECLARED \
  "${CLI[@]}" doctor --root "${TMP_DIR}/foreign/vendor/example" --workspace-root "${TMP_DIR}/foreign"

# A workspace root that is not there at all must answer with its own code, not an unhandled throw.
ws_refuses 'doctor threw on a missing workspace root' WORKSPACE_PACKAGE_JSON_MISSING \
  "${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${TMP_DIR}/no-such-workspace"

# The workspace manifest gets the same shape and bounded-path scrutiny as the app's own: a corrupt
# one silently coerced to an empty object would take every package-manager check with it.
cat > "${WS_DIR}/package.json" <<'JSON'
[]
JSON
ws_refuses 'doctor accepted a non-object workspace package.json' PACKAGE_JSON_INVALID \
  "${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}"
write_workspace_root

# The lockfile actually read is the workspace one, so it is the one that must be bounded.
mv "${WS_DIR}/yarn.lock" "${TMP_DIR}/detached-yarn.lock"
ln -s "${TMP_DIR}/detached-yarn.lock" "${WS_DIR}/yarn.lock"
ws_refuses 'doctor followed a workspace lockfile symlink out of the tree' CONTROL_PATH_SYMLINK \
  "${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}"
rm "${WS_DIR}/yarn.lock"
mv "${TMP_DIR}/detached-yarn.lock" "${WS_DIR}/yarn.lock"

# Both declarations bind. The root's decides the install; the app's is what runs from the app
# directory, so an app-level contradiction is a mismatch even when the root agrees with the manifest.
node --input-type=module - "${WS_DIR}/apps/example/package.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const packageJson = JSON.parse(readFileSync(path, 'utf8'));
packageJson.packageManager = 'npm@10.8.0';
writeFileSync(path, `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
ws_refuses 'doctor ignored the app package manager' PACKAGE_MANAGER_MISMATCH \
  "${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}"

node --input-type=module - "${WS_DIR}/apps/example/package.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const packageJson = JSON.parse(readFileSync(path, 'utf8'));
packageJson.packageManager = 'yarn@1.22.22';
writeFileSync(path, `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
"${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}" --json \
  > "${TMP_DIR}/workspace-agreeing-manager.json"
grep -q '"status": "READY"' "${TMP_DIR}/workspace-agreeing-manager.json"

write_workspace_root '"packageManager": "npm@10.8.0"'
ws_refuses 'doctor ignored the workspace package manager' PACKAGE_MANAGER_MISMATCH \
  "${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}"

# Leave the fixture verifiable, so a case appended after this one starts from a passing app.
write_workspace_root
"${CLI[@]}" doctor --root "${WS_DIR}/apps/example" --workspace-root "${WS_DIR}" --json \
  > "${TMP_DIR}/workspace-restored.json"
grep -q '"status": "READY"' "${TMP_DIR}/workspace-restored.json"


"${CLI[@]}" migration validate --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" --json > "${TMP_DIR}/migration-validation.json"
assert_protocol "${TMP_DIR}/migration-validation.json" migration-validation-result
MIGRATION_DIGEST="$(shasum -a 256 "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" | cut -d ' ' -f 1)"
grep -q "\"descriptorDigest\": \"${MIGRATION_DIGEST}\"" "${TMP_DIR}/migration-validation.json"

INDEX_BEFORE="$(index_snapshot "${APP_DIR}")"
"${CLI[@]}" migration plan \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${APP_DIR}" --registry "${TMP_DIR}/plan-registry.json" \
  --expected-head "${APP_HEAD}" > "${TMP_DIR}/plan.json"
assert_protocol "${TMP_DIR}/plan.json" migration-plan-result
grep -q '"status": "APPLICABLE"' "${TMP_DIR}/plan.json"
grep -q "\"head\": \"${APP_HEAD}\"" "${TMP_DIR}/plan.json"
MANIFEST_DIGEST="$(shasum -a 256 "${APP_DIR}/.corva/ai-tools.json" | cut -d ' ' -f 1)"
PACKAGE_DIGEST="$(shasum -a 256 "${APP_DIR}/package.json" | cut -d ' ' -f 1)"
LOCKFILE_DIGEST="$(shasum -a 256 "${APP_DIR}/yarn.lock" | cut -d ' ' -f 1)"
grep -q "\"manifestDigest\": \"${MANIFEST_DIGEST}\"" "${TMP_DIR}/plan.json"
grep -q "\"packageDigest\": \"${PACKAGE_DIGEST}\"" "${TMP_DIR}/plan.json"
grep -q "\"digest\": \"${LOCKFILE_DIGEST}\"" "${TMP_DIR}/plan.json"
REGISTRY_DIGEST="$(shasum -a 256 "${TMP_DIR}/plan-registry.json" | cut -d ' ' -f 1)"
grep -q "\"digest\": \"${REGISTRY_DIGEST}\"" "${TMP_DIR}/plan.json"
test "${INDEX_BEFORE}" = "$(index_snapshot "${APP_DIR}")"
test -z "$(git -C "${APP_DIR}" status --porcelain=v1 --untracked-files=all)"

NONGIT_DIR="${TMP_DIR}/non-git-example"
mkdir -p "${NONGIT_DIR}/.corva" "${NONGIT_DIR}/src/ai-tools"
cp "${APP_DIR}/.corva/ai-tools.json" "${NONGIT_DIR}/.corva/ai-tools.json"
cp "${APP_DIR}/manifest.json" "${APP_DIR}/package.json" "${APP_DIR}/yarn.lock" "${NONGIT_DIR}/"
cp "${APP_DIR}/src/ai-tools/README.md" "${NONGIT_DIR}/src/ai-tools/README.md"
if "${CLI[@]}" migration plan --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${NONGIT_DIR}" --registry "${TMP_DIR}/plan-registry.json" > "${TMP_DIR}/non-git-plan.json"; then
  echo 'fleet test: migration plan accepted a non-Git checkout' >&2
  exit 1
fi
grep -q 'GIT_REPOSITORY_REQUIRED' "${TMP_DIR}/non-git-plan.json"
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/non-git-plan.json" > /dev/null

MISSING_DIR="${TMP_DIR}/missing-manifest-example"
mkdir -p "${MISSING_DIR}"
printf 'fixture\n' > "${MISSING_DIR}/README.md"
git -C "${MISSING_DIR}" init -q
git -C "${MISSING_DIR}" config user.email fleet-test@corva.ai
git -C "${MISSING_DIR}" config user.name 'Fleet test'
git -C "${MISSING_DIR}" remote add origin git@github.com:corva-ai/corva-dc-fe-missing.git
git -C "${MISSING_DIR}" add README.md
git -C "${MISSING_DIR}" commit -qm fixture
if "${CLI[@]}" migration plan --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${MISSING_DIR}" --registry "${TMP_DIR}/plan-registry.json" > "${TMP_DIR}/missing-plan.json"; then
  echo 'fleet test: migration plan accepted a checkout without a manifest' >&2
  exit 1
fi
grep -q 'MANIFEST_MISSING' "${TMP_DIR}/missing-plan.json"
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/missing-plan.json" > /dev/null

fail_if_passes 'migration plan accepted a stale expected HEAD' "${CLI[@]}" migration plan \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" --root "${APP_DIR}" \
  --registry "${TMP_DIR}/plan-registry.json" --expected-head 0000000000000000000000000000000000000000

if "${CLI[@]}" migration plan --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${APP_DIR}" --registry "${TMP_DIR}/plan-registry.json" --expected-headd "${APP_HEAD}" \
  > "${TMP_DIR}/unknown-option.json"; then
  echo 'fleet test: migration plan ignored an unknown option' >&2
  exit 1
fi
grep -q 'USAGE_ERROR' "${TMP_DIR}/unknown-option.json"

fail_if_passes 'fleet inspect ignored a misspelled strict option' "${CLI[@]}" fleet inspect \
  --registry "${TMP_DIR}/plan-registry.json" --checkouts "${TMP_DIR}" --strcit
fail_if_passes 'migration validate accepted a positional argument' "${CLI[@]}" migration validate \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" stray-positional
fail_if_passes 'migration validate accepted a duplicate option' "${CLI[@]}" migration validate \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json"

printf 'dirty\n' > "${APP_DIR}/untracked.txt"
fail_if_passes 'migration plan accepted a dirty checkout' "${CLI[@]}" migration plan \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" --root "${APP_DIR}" \
  --registry "${TMP_DIR}/plan-registry.json"
rm "${APP_DIR}/untracked.txt"

git -C "${APP_DIR}" remote set-url origin https://evil.example/path/github.com/corva-ai/corva-dc-fe-example.git
fail_if_passes 'migration plan accepted a lookalike GitHub origin' "${CLI[@]}" migration plan \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" --root "${APP_DIR}" \
  --registry "${TMP_DIR}/plan-registry.json"
git -C "${APP_DIR}" remote set-url origin git@github.com:corva-ai/corva-dc-fe-example.git

git -C "${APP_DIR}" remote set-url --push origin git@github.com:corva-ai/different-repository.git
fail_if_passes 'migration plan accepted a mismatched push origin' "${CLI[@]}" migration plan \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" --root "${APP_DIR}" \
  --registry "${TMP_DIR}/plan-registry.json"
git -C "${APP_DIR}" remote set-url --push origin git@github.com:corva-ai/corva-dc-fe-example.git

mv "${APP_DIR}/package.json" "${TMP_DIR}/outside-package.json"
ln -s "${TMP_DIR}/outside-package.json" "${APP_DIR}/package.json"
fail_if_passes 'migration plan accepted a symlinked package.json' "${CLI[@]}" migration plan \
  --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" --root "${APP_DIR}" \
  --registry "${TMP_DIR}/plan-registry.json"
rm "${APP_DIR}/package.json"
mv "${TMP_DIR}/outside-package.json" "${APP_DIR}/package.json"

cat > "${TMP_DIR}/blocked-registry.json" <<'JSON'
{
  "schemaVersion": 1,
  "repositories": [
    {
      "repository": "corva-ai/corva-dc-fe-example",
      "profile": "dc-app",
      "rolloutGroup": "blocked",
      "reason": "Manual hold"
    }
  ]
}
JSON

cat > "${TMP_DIR}/invalid-blocked-registry.json" <<'JSON'
{
  "schemaVersion": 1,
  "repositories": [
    { "repository": "corva-ai/corva-dc-fe-example", "profile": "dc-app", "rolloutGroup": "blocked" }
  ]
}
JSON
fail_if_passes 'registry schema accepted blocked without reason' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/registry.schema.json" "${TMP_DIR}/invalid-blocked-registry.json"
if "${CLI[@]}" fleet inspect --registry "${TMP_DIR}/invalid-blocked-registry.json" \
  --checkouts "${TMP_DIR}" --json > "${TMP_DIR}/invalid-blocked-error.json"; then
  echo 'fleet test: runtime accepted blocked without reason' >&2
  exit 1
fi
grep -q 'BLOCK_REASON_REQUIRED' "${TMP_DIR}/invalid-blocked-error.json"

"${CLI[@]}" fleet inspect --registry "${TMP_DIR}/blocked-registry.json" --checkouts "${TMP_DIR}" --json > "${TMP_DIR}/blocked.json"
assert_protocol "${TMP_DIR}/blocked.json" fleet-inspection-result
grep -q '"BLOCKED_BY_POLICY": 1' "${TMP_DIR}/blocked.json"
fail_if_passes 'strict inspection accepted a blocked repository' "${CLI[@]}" fleet inspect \
  --registry "${TMP_DIR}/blocked-registry.json" --checkouts "${TMP_DIR}" --strict
if "${CLI[@]}" migration plan --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${APP_DIR}" --registry "${TMP_DIR}/blocked-registry.json" > "${TMP_DIR}/blocked-plan.json"; then
  echo 'fleet test: migration plan accepted a blocked repository' >&2
  exit 1
fi
grep -q 'ROLLOUT_BLOCKED' "${TMP_DIR}/blocked-plan.json"

cat > "${TMP_DIR}/paused-registry.json" <<'JSON'
{
  "schemaVersion": 1,
  "paused": true,
  "pauseReason": "Incident response",
  "repositories": [
    { "repository": "corva-ai/corva-dc-fe-example", "profile": "dc-app", "rolloutGroup": "canary" }
  ]
}
JSON

"${CLI[@]}" fleet inspect --registry "${TMP_DIR}/paused-registry.json" --checkouts "${TMP_DIR}" --json > "${TMP_DIR}/paused.json"
grep -q '"FLEET_PAUSED": 1' "${TMP_DIR}/paused.json"
fail_if_passes 'strict inspection accepted a fleet pause' "${CLI[@]}" fleet inspect \
  --registry "${TMP_DIR}/paused-registry.json" --checkouts "${TMP_DIR}" --strict
if "${CLI[@]}" migration plan --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${APP_DIR}" --registry "${TMP_DIR}/paused-registry.json" > "${TMP_DIR}/paused-plan.json"; then
  echo 'fleet test: migration plan accepted a fleet pause' >&2
  exit 1
fi
grep -q 'FLEET_PAUSED' "${TMP_DIR}/paused-plan.json"

cat > "${TMP_DIR}/unsafe-migration.json" <<'JSON'
{
  "schemaVersion": 2,
  "id": "unsafe-dependency",
  "from": { "kitRange": "^1.4.0", "contractRevision": 2, "canonRevision": 8 },
  "to": { "kitRange": "^0.1.0", "contractRevision": 2, "canonRevision": 8 },
  "impact": "dependency-only",
  "allowedPaths": [".npmrc", "src/runtime.js"],
  "requiresSourceReview": false,
  "requiresLiveValidation": false,
  "notes": { "not": "a string" }
}
JSON

fail_if_passes 'migration validation accepted downgrade, sensitive/source paths, or invalid notes' "${CLI[@]}" migration validate \
  --file "${TMP_DIR}/unsafe-migration.json"
if "${CLI[@]}" migration validate --file "${TMP_DIR}/unsafe-migration.json" --json > "${TMP_DIR}/unsafe-error.json"; then
  echo 'fleet test: JSON migration validation accepted unsafe descriptor' >&2
  exit 1
fi
assert_protocol "${TMP_DIR}/unsafe-error.json" error-result
grep -q 'KIT_DOWNGRADE_FORBIDDEN' "${TMP_DIR}/unsafe-error.json"
grep -q 'SENSITIVE_PATH_FORBIDDEN' "${TMP_DIR}/unsafe-error.json"
grep -q 'DEPENDENCY_ONLY_PATH_INVALID' "${TMP_DIR}/unsafe-error.json"

node --input-type=module - "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" "${TMP_DIR}/legacy-migration.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const [source, target] = process.argv.slice(2);
const migration = JSON.parse(readFileSync(source, 'utf8'));
migration.schemaVersion = 1;
delete migration.from.kitRange;
writeFileSync(target, `${JSON.stringify(migration, null, 2)}\n`);
NODE
if "${CLI[@]}" migration validate --file "${TMP_DIR}/legacy-migration.json" --json > "${TMP_DIR}/legacy-validation.json"; then
  echo 'fleet test: legacy migration was treated as directly plannable' >&2
  exit 1
fi
grep -q 'MIGRATION_UPGRADE_REQUIRED' "${TMP_DIR}/legacy-validation.json"

cat > "${TMP_DIR}/bad-protocol-type.json" <<'JSON'
{
  "schemaVersion": 1,
  "kind": "doctor-result",
  "status": "READY",
  "root": null,
  "manifestPath": 42,
  "issues": []
}
JSON
fail_if_passes 'protocol schema accepted invalid field types' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/bad-protocol-type.json"

cat > "${TMP_DIR}/bad-protocol-code.json" <<'JSON'
{
  "schemaVersion": 1,
  "kind": "error-result",
  "status": "ERROR",
  "issues": [{ "code": "UNKNOWN_FUTURE_CODE", "message": "unknown" }]
}
JSON
fail_if_passes 'protocol schema accepted an unknown issue code' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/bad-protocol-code.json"

cat > "${TMP_DIR}/bad-doctor-ready.json" <<'JSON'
{
  "schemaVersion": 1,
  "kind": "doctor-result",
  "status": "READY",
  "root": "/tmp/example",
  "manifestPath": "/tmp/example/.corva/ai-tools.json",
  "issues": []
}
JSON
fail_if_passes 'protocol schema accepted READY without evidence' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/bad-doctor-ready.json"

node --input-type=module - "${TMP_DIR}/plan.json" "${TMP_DIR}/bad-plan-applicable.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const [source, target] = process.argv.slice(2);
const result = JSON.parse(readFileSync(source, 'utf8'));
result.observed.clean = false;
result.observed.head = null;
result.issues = [{ code: 'GIT_WORKTREE_DIRTY', message: 'dirty' }];
writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`);
NODE
fail_if_passes 'protocol schema accepted contradictory APPLICABLE evidence' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/bad-plan-applicable.json"

node --input-type=module - "${TMP_DIR}/migration-validation.json" "${TMP_DIR}/bad-validation-valid.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const [source, target] = process.argv.slice(2);
const result = JSON.parse(readFileSync(source, 'utf8'));
result.issues = [{ code: 'MIGRATION_TYPE_INVALID', message: 'invalid' }];
writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`);
NODE
fail_if_passes 'protocol schema accepted VALID with issues' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/bad-validation-valid.json"

cat > "${TMP_DIR}/bad-error-empty.json" <<'JSON'
{
  "schemaVersion": 1,
  "kind": "error-result",
  "status": "ERROR",
  "issues": []
}
JSON
fail_if_passes 'protocol schema accepted ERROR without issues' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/bad-error-empty.json"

node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/app-manifest.schema.json" "${TMP_DIR}/dry-run.json" \
  "${ROOT_DIR}/fleet/registry.schema.json" "${TMP_DIR}/blocked-registry.json" \
  "${ROOT_DIR}/fleet/registry.schema.json" "${TMP_DIR}/paused-registry.json" \
  "${ROOT_DIR}/fleet/migration.schema.json" "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/doctor.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/migration-validation.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/plan.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/blocked.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/paused.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/unsafe-error.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/legacy-validation.json" > /dev/null

node --input-type=module - "${APP_DIR}/.corva/ai-tools.json" "${TMP_DIR}/dot-path-manifest.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const [source, target] = process.argv.slice(2);
const manifest = JSON.parse(readFileSync(source, 'utf8'));
manifest.toolRoots = ['src/./ai-tools'];
writeFileSync(source, `${JSON.stringify(manifest, null, 2)}\n`);
writeFileSync(target, `${JSON.stringify(manifest, null, 2)}\n`);
NODE
fail_if_passes 'manifest schema accepted a dot path segment' node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/app-manifest.schema.json" "${TMP_DIR}/dot-path-manifest.json"
fail_if_passes 'doctor accepted a dot path segment' "${CLI[@]}" doctor --root "${APP_DIR}"
git -C "${APP_DIR}" checkout -q -- .corva/ai-tools.json

node --input-type=module - "${APP_DIR}/package.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const packageJson = JSON.parse(readFileSync(path, 'utf8'));
packageJson.packageManager = 'yarn@4.9.2';
writeFileSync(path, `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
if "${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/yarn-berry.json"; then
  echo 'fleet test: doctor accepted unsupported Yarn Berry' >&2
  exit 1
fi
grep -q 'PACKAGE_MANAGER_VERSION_UNSUPPORTED' "${TMP_DIR}/yarn-berry.json"

node --input-type=module - "${APP_DIR}/package.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const packageJson = JSON.parse(readFileSync(path, 'utf8'));
packageJson.packageManager = 'npm@10.8.0';
writeFileSync(path, `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
if "${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/manager-mismatch.json"; then
  echo 'fleet test: doctor accepted a package-manager mismatch' >&2
  exit 1
fi
grep -q 'PACKAGE_MANAGER_MISMATCH' "${TMP_DIR}/manager-mismatch.json"
git -C "${APP_DIR}" checkout -q -- package.json

node --input-type=module - "${APP_DIR}/.corva/ai-tools.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const manifest = JSON.parse(readFileSync(path, 'utf8'));
manifest.verification.command = 'npm run ai-tools:verify';
writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
NODE
rm "${APP_DIR}/yarn.lock"
cat > "${APP_DIR}/package-lock.json" <<'JSON'
{
  "name": "example-app",
  "lockfileVersion": 3,
  "packages": {
    "": { "dependencies": { "@corva/fe-ai-tools": "^1.4.0" } },
    "node_modules/@corva/fe-ai-tools": { "version": "1.4.0" }
  }
}
JSON
"${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/npm-doctor.json"
grep -q '"resolvedVersion": "1.4.0"' "${TMP_DIR}/npm-doctor.json"

node --input-type=module - "${APP_DIR}/.corva/ai-tools.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const manifest = JSON.parse(readFileSync(path, 'utf8'));
manifest.verification.command = 'pnpm ai-tools:verify';
writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
NODE
node --input-type=module - "${APP_DIR}/package.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const packageJson = JSON.parse(readFileSync(path, 'utf8'));
packageJson.packageManager = 'pnpm@9.15.0';
writeFileSync(path, `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
rm "${APP_DIR}/package-lock.json"
cat > "${APP_DIR}/pnpm-lock.yaml" <<'YAML'
lockfileVersion: '9.0'
packages:
  '@corva/fe-ai-tools@1.4.0': {}
YAML
"${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/pnpm-doctor.json"
grep -q '"resolvedVersion": "1.4.0"' "${TMP_DIR}/pnpm-doctor.json"

rm "${APP_DIR}/pnpm-lock.yaml"
git -C "${APP_DIR}" checkout -q -- .corva/ai-tools.json package.json yarn.lock

printf '"bad"\n' > "${APP_DIR}/.corva/ai-tools.json"
if "${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/malformed-top-level.json"; then
  echo 'fleet test: doctor accepted a non-object manifest' >&2
  exit 1
fi
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/malformed-top-level.json" > /dev/null
grep -q 'MANIFEST_TYPE_INVALID' "${TMP_DIR}/malformed-top-level.json"
git -C "${APP_DIR}" checkout -q -- .corva/ai-tools.json

printf 'null\n' > "${APP_DIR}/package.json"
if "${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/malformed-package.json"; then
  echo 'fleet test: doctor accepted a non-object package.json' >&2
  exit 1
fi
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/malformed-package.json" > /dev/null
grep -q 'PACKAGE_JSON_INVALID' "${TMP_DIR}/malformed-package.json"
git -C "${APP_DIR}" checkout -q -- package.json

node --input-type=module - "${APP_DIR}/.corva/ai-tools.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const manifest = JSON.parse(readFileSync(path, 'utf8'));
manifest.toolRoots = [42];
writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
NODE

if "${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/malformed.json"; then
  echo 'fleet test: doctor accepted malformed toolRoots' >&2
  exit 1
fi
assert_protocol "${TMP_DIR}/malformed.json" doctor-result
grep -q 'TOOL_ROOT_PATH_INVALID' "${TMP_DIR}/malformed.json"
git -C "${APP_DIR}" checkout -q -- .corva/ai-tools.json

node --input-type=module - "${APP_DIR}/.corva/ai-tools.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const manifest = JSON.parse(readFileSync(path, 'utf8'));
manifest.toolRoots = ['linked-tools'];
writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
NODE
ln -s "${TMP_DIR}" "${APP_DIR}/linked-tools"
fail_if_passes 'doctor accepted a tool-root symlink outside the repository' "${CLI[@]}" doctor --root "${APP_DIR}"
rm "${APP_DIR}/linked-tools"

git -C "${APP_DIR}" checkout -q -- .corva/ai-tools.json
mv "${APP_DIR}/.corva/ai-tools.json" "${TMP_DIR}/external-ai-tools.json"
ln -s "${TMP_DIR}/external-ai-tools.json" "${APP_DIR}/.corva/ai-tools.json"
if "${CLI[@]}" doctor --root "${APP_DIR}" --json > "${TMP_DIR}/manifest-symlink-doctor.json"; then
  echo 'fleet test: doctor accepted a manifest symlink outside the repository' >&2
  exit 1
fi
grep -q 'CONTROL_PATH_SYMLINK' "${TMP_DIR}/manifest-symlink-doctor.json"
if "${CLI[@]}" fleet inspect --registry "${TMP_DIR}/plan-registry.json" --checkouts "${TMP_DIR}" --strict --json > "${TMP_DIR}/manifest-symlink-fleet.json"; then
  echo 'fleet test: strict fleet inspection accepted a manifest symlink' >&2
  exit 1
fi
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/manifest-symlink-doctor.json" \
  "${ROOT_DIR}/fleet/protocol-result.schema.json" "${TMP_DIR}/manifest-symlink-fleet.json" > /dev/null
rm "${APP_DIR}/.corva/ai-tools.json"
mv "${TMP_DIR}/external-ai-tools.json" "${APP_DIR}/.corva/ai-tools.json"

HEALTH_CHECKOUTS="${TMP_DIR}/health-checkouts"
mkdir -p "${HEALTH_CHECKOUTS}"
mv "${V2_DIR}" "${HEALTH_CHECKOUTS}/corva-dc-fe-example"
git -C "${HEALTH_CHECKOUTS}/corva-dc-fe-example" init -q
git -C "${HEALTH_CHECKOUTS}/corva-dc-fe-example" config user.email fleet-test@corva.ai
git -C "${HEALTH_CHECKOUTS}/corva-dc-fe-example" config user.name 'Fleet test'
git -C "${HEALTH_CHECKOUTS}/corva-dc-fe-example" remote add origin git@github.com:corva-ai/corva-dc-fe-example.git
git -C "${HEALTH_CHECKOUTS}/corva-dc-fe-example" add .
git -C "${HEALTH_CHECKOUTS}/corva-dc-fe-example" commit -qm fixture
REPORT_DIGEST="$(shasum -a 256 "${HEALTH_CHECKOUTS}/corva-dc-fe-example/ai-tests/runs/fixture/report.md" | cut -d ' ' -f 1)"
cat > "${TMP_DIR}/observations.json" <<JSON
{
  "schemaVersion": 1,
  "observations": [{
    "repository": "corva-ai/corva-dc-fe-example",
    "report": "ai-tests/runs/fixture/report.md",
    "reportDigest": "${REPORT_DIGEST}",
    "turns": 10, "toolCalls": 8, "recoveries": 2, "guessedParameters": 1,
    "contextOnlyAnswers": 1, "zeroToolFailures": 0, "tokens": 1200,
    "sendToFirstEventMsP50": 900, "defectClasses": ["label-resolution"]
  }]
}
JSON
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/interaction-observations.schema.json" "${TMP_DIR}/observations.json" >/dev/null
"${CLI[@]}" fleet health --registry "${TMP_DIR}/plan-registry.json" --checkouts "${HEALTH_CHECKOUTS}" \
  --observations "${TMP_DIR}/observations.json" > "${TMP_DIR}/health.json"
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/health-report.schema.json" "${TMP_DIR}/health.json" >/dev/null
grep -q '"coverageStatus": "full"' "${TMP_DIR}/health.json"
grep -Eq '"registryDigest": "[a-f0-9]{64}"' "${TMP_DIR}/health.json"
grep -Eq '"observationsDigest": "[a-f0-9]{64}"' "${TMP_DIR}/health.json"
grep -q '"entity-scope": 1' "${TMP_DIR}/health.json"
grep -q '"recoveries": 2' "${TMP_DIR}/health.json"
grep -q '"sendToFirstEventMsP50Median": 900' "${TMP_DIR}/health.json"
grep -q '"label-resolution": 1' "${TMP_DIR}/health.json"
"${CLI[@]}" fleet health --registry "${TMP_DIR}/plan-registry.json" --checkouts "${HEALTH_CHECKOUTS}" \
  --observations "${TMP_DIR}/observations.json" --markdown > "${TMP_DIR}/health.md"
grep -q '^# Ask Corva app-tool fleet health$' "${TMP_DIR}/health.md"
grep -q '| corva-ai/corva-dc-fe-example | READY | 2 | 1.4.0 | 8 | full | 1 |' "${TMP_DIR}/health.md"

cp "${TMP_DIR}/observations.json" "${TMP_DIR}/observations-invalid.json"
node --input-type=module - "${TMP_DIR}/observations-invalid.json" <<'NODE'
import { readFileSync, writeFileSync } from 'node:fs';
const path = process.argv[2];
const value = JSON.parse(readFileSync(path, 'utf8'));
value.observations[0].reportDigest = '0'.repeat(64);
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
NODE
fail_if_passes 'fleet health accepted an observation whose report digest did not match' \
  "${CLI[@]}" fleet health --registry "${TMP_DIR}/plan-registry.json" --checkouts "${HEALTH_CHECKOUTS}" \
  --observations "${TMP_DIR}/observations-invalid.json"

"${CLI[@]}" migration packet --file "${ROOT_DIR}/fleet/migrations/example-dependency-only.json" \
  --root "${APP_DIR}" --registry "${TMP_DIR}/plan-registry.json" --expected-head "$(git -C "${APP_DIR}" rev-parse HEAD)" \
  > "${TMP_DIR}/packet.json"
grep -q '"kind": "migration-execution-packet"' "${TMP_DIR}/packet.json"
grep -q '"draft": true' "${TMP_DIR}/packet.json"
grep -q '"autoMerge": false' "${TMP_DIR}/packet.json"
grep -Eq '"packetDigest": "[a-f0-9]{64}"' "${TMP_DIR}/packet.json"
node "${ROOT_DIR}/scripts/fleet/validate-schemas.mjs" \
  "${ROOT_DIR}/fleet/execution-packet.schema.json" "${TMP_DIR}/packet.json" >/dev/null
node --input-type=module - "${TMP_DIR}/packet.json" <<'NODE'
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
const packet = JSON.parse(readFileSync(process.argv[2], 'utf8'));
const claimed = packet.packetDigest;
delete packet.packetDigest;
const actual = createHash('sha256').update(JSON.stringify(packet)).digest('hex');
if (claimed !== actual) process.exit(1);
NODE

grep -q "declaredManager !== manager" "${ROOT_DIR}/.github/workflows/app-tool-validation.yml"
grep -q "supports Yarn 1 only" "${ROOT_DIR}/.github/workflows/app-tool-validation.yml"
grep -q -- "--ignore-scripts" "${ROOT_DIR}/.github/workflows/app-tool-validation.yml"
if grep -q '\${{ secrets.NPM_TOKEN }}' "${ROOT_DIR}/.github/workflows/app-tool-validation.yml"; then
  echo 'fleet test: reusable workflow exposes NPM_TOKEN to an untrusted checkout' >&2
  exit 1
fi
grep -q 'yarn install --frozen-lockfile' "${ROOT_DIR}/.github/actions/setup-environment/action.yml"
if grep -Eq 'path:[[:space:]]*node_modules|cache-hit' "${ROOT_DIR}/.github/actions/setup-environment/action.yml"; then
  echo 'fleet test: release setup can reuse node_modules or skip frozen-lock validation' >&2
  exit 1
fi
if grep -q 'function digest(path)' "${ROOT_DIR}/scripts/fleet/cli.mjs"; then
  echo 'fleet test: path-based digest helper was reintroduced' >&2
  exit 1
fi

echo 'fleet cli: ok'
