Harden shared lifecycle rollback
This commit is contained in:
206
bin/context-kit
206
bin/context-kit
@@ -64,6 +64,13 @@ SHARED_SERVICES=(searxng "${WEB_SEARCH_SERVICE_NAME}" "${DOCS_SERVICE_NAME}")
|
|||||||
SNAPSHOT_IDS=()
|
SNAPSHOT_IDS=()
|
||||||
SNAPSHOT_RUNNING=()
|
SNAPSHOT_RUNNING=()
|
||||||
DOCS_SOURCES_FILE="${DATA_DIR}/docs-sources.txt"
|
DOCS_SOURCES_FILE="${DATA_DIR}/docs-sources.txt"
|
||||||
|
DOCS_SOURCES_TRANSACTION_ACTIVE=0
|
||||||
|
DOCS_SOURCES_PRIOR_PRESENT=0
|
||||||
|
DOCS_SOURCES_BACKUP_DIR=''
|
||||||
|
DOCS_SOURCES_BACKUP_FILE=''
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_ACTIVE=0
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_REMOVE_NEW=false
|
||||||
DOCS_DATA_DIR="${DATA_DIR}/docs"
|
DOCS_DATA_DIR="${DATA_DIR}/docs"
|
||||||
MODELS_DATA_DIR="${DATA_DIR}/models"
|
MODELS_DATA_DIR="${DATA_DIR}/models"
|
||||||
DOCS_LOCAL_SOURCES_DIR="${CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR:-${DATA_DIR}/local-sources}"
|
DOCS_LOCAL_SOURCES_DIR="${CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR:-${DATA_DIR}/local-sources}"
|
||||||
@@ -130,12 +137,104 @@ require_no_args() {
|
|||||||
|
|
||||||
write_docs_sources_file() {
|
write_docs_sources_file() {
|
||||||
mkdir -p "$(dirname "${DOCS_SOURCES_FILE}")"
|
mkdir -p "$(dirname "${DOCS_SOURCES_FILE}")"
|
||||||
local tmp="${DOCS_SOURCES_FILE}.tmp.$$"
|
local tmp="${DOCS_SOURCES_FILE}.tmp.${BASHPID}"
|
||||||
{
|
DOCS_SOURCES_RENDER_TMP="${tmp}"
|
||||||
|
if ! {
|
||||||
printf '# generated by context-kit lifecycle commands; edit your CONTEXT_KIT_DOCS_SOURCES file(s) instead\n'
|
printf '# generated by context-kit lifecycle commands; edit your CONTEXT_KIT_DOCS_SOURCES file(s) instead\n'
|
||||||
resolved_sources
|
resolved_sources
|
||||||
} > "${tmp}"
|
} > "${tmp}"; then
|
||||||
mv "${tmp}" "${DOCS_SOURCES_FILE}"
|
rm -f -- "${tmp}"
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! mv -fT -- "${tmp}" "${DOCS_SOURCES_FILE}"; then
|
||||||
|
rm -f -- "${tmp}"
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_docs_sources_transaction() {
|
||||||
|
DOCS_SOURCES_TRANSACTION_ACTIVE=0
|
||||||
|
DOCS_SOURCES_PRIOR_PRESENT=0
|
||||||
|
DOCS_SOURCES_BACKUP_DIR=''
|
||||||
|
DOCS_SOURCES_BACKUP_FILE=''
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
}
|
||||||
|
|
||||||
|
begin_docs_sources_transaction() {
|
||||||
|
[[ "${DOCS_SOURCES_TRANSACTION_ACTIVE}" -eq 0 ]] || return 1
|
||||||
|
mkdir -p "$(dirname "${DOCS_SOURCES_FILE}")"
|
||||||
|
|
||||||
|
local backup_dir
|
||||||
|
backup_dir="$(mktemp -d "${DOCS_SOURCES_FILE}.lifecycle-backup.XXXXXX")" || return 1
|
||||||
|
DOCS_SOURCES_BACKUP_DIR="${backup_dir}"
|
||||||
|
DOCS_SOURCES_BACKUP_FILE="${backup_dir}/docs-sources.txt"
|
||||||
|
DOCS_SOURCES_PRIOR_PRESENT=0
|
||||||
|
DOCS_SOURCES_TRANSACTION_ACTIVE=1
|
||||||
|
|
||||||
|
if [[ -e "${DOCS_SOURCES_FILE}" || -L "${DOCS_SOURCES_FILE}" ]]; then
|
||||||
|
DOCS_SOURCES_PRIOR_PRESENT=1
|
||||||
|
if ! mv -T -- "${DOCS_SOURCES_FILE}" "${DOCS_SOURCES_BACKUP_FILE}"; then
|
||||||
|
rmdir -- "${DOCS_SOURCES_BACKUP_DIR}" 2>/dev/null || true
|
||||||
|
reset_docs_sources_transaction
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_docs_sources_transaction() {
|
||||||
|
[[ "${DOCS_SOURCES_TRANSACTION_ACTIVE}" -eq 1 ]] || return 0
|
||||||
|
local failed=0
|
||||||
|
|
||||||
|
if [[ -n "${DOCS_SOURCES_RENDER_TMP}" ]]; then
|
||||||
|
rm -f -- "${DOCS_SOURCES_RENDER_TMP}" || failed=1
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
fi
|
||||||
|
if [[ "${DOCS_SOURCES_PRIOR_PRESENT}" -eq 1 ]]; then
|
||||||
|
if [[ -e "${DOCS_SOURCES_BACKUP_FILE}" || -L "${DOCS_SOURCES_BACKUP_FILE}" ]]; then
|
||||||
|
if ! mv -fT -- "${DOCS_SOURCES_BACKUP_FILE}" "${DOCS_SOURCES_FILE}"; then
|
||||||
|
warn "failed to restore prior docs sources file: ${DOCS_SOURCES_FILE}"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "prior docs sources backup is missing: ${DOCS_SOURCES_BACKUP_FILE}"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
elif ! rm -f -- "${DOCS_SOURCES_FILE}"; then
|
||||||
|
warn "failed to restore prior absence of docs sources file: ${DOCS_SOURCES_FILE}"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${failed}" -eq 0 ]] && ! rmdir -- "${DOCS_SOURCES_BACKUP_DIR}"; then
|
||||||
|
warn "failed to remove docs sources backup directory: ${DOCS_SOURCES_BACKUP_DIR}"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
if [[ "${failed}" -eq 0 ]]; then
|
||||||
|
reset_docs_sources_transaction
|
||||||
|
fi
|
||||||
|
return "${failed}"
|
||||||
|
}
|
||||||
|
|
||||||
|
discard_docs_sources_transaction() {
|
||||||
|
[[ "${DOCS_SOURCES_TRANSACTION_ACTIVE}" -eq 1 ]] || return 0
|
||||||
|
local failed=0
|
||||||
|
|
||||||
|
if [[ -n "${DOCS_SOURCES_RENDER_TMP}" ]]; then
|
||||||
|
rm -f -- "${DOCS_SOURCES_RENDER_TMP}" || failed=1
|
||||||
|
DOCS_SOURCES_RENDER_TMP=''
|
||||||
|
fi
|
||||||
|
if [[ "${DOCS_SOURCES_PRIOR_PRESENT}" -eq 1 ]]; then
|
||||||
|
rm -f -- "${DOCS_SOURCES_BACKUP_FILE}" || failed=1
|
||||||
|
fi
|
||||||
|
if [[ "${failed}" -eq 0 ]] && ! rmdir -- "${DOCS_SOURCES_BACKUP_DIR}"; then
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
if [[ "${failed}" -eq 0 ]]; then
|
||||||
|
reset_docs_sources_transaction
|
||||||
|
fi
|
||||||
|
return "${failed}"
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_writable_dir() {
|
ensure_writable_dir() {
|
||||||
@@ -491,6 +590,34 @@ restore_shared_service_states() {
|
|||||||
return "${failed}"
|
return "${failed}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rollback_lifecycle_transaction() {
|
||||||
|
local failed=0
|
||||||
|
restore_docs_sources_transaction || failed=1
|
||||||
|
if [[ "${LIFECYCLE_STATE_ROLLBACK_ACTIVE}" -eq 1 ]]; then
|
||||||
|
restore_shared_service_states "${LIFECYCLE_STATE_ROLLBACK_REMOVE_NEW}" || failed=1
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_ACTIVE=0
|
||||||
|
fi
|
||||||
|
return "${failed}"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle_transaction_exit() {
|
||||||
|
local status="$1"
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
rollback_lifecycle_transaction || true
|
||||||
|
exit "${status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
arm_lifecycle_transaction_traps() {
|
||||||
|
trap 'lifecycle_transaction_exit "$?"' EXIT
|
||||||
|
trap 'exit 129' HUP
|
||||||
|
trap 'exit 130' INT
|
||||||
|
trap 'exit 143' TERM
|
||||||
|
}
|
||||||
|
|
||||||
|
disarm_lifecycle_transaction_traps() {
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
}
|
||||||
|
|
||||||
shared_services_ready() {
|
shared_services_ready() {
|
||||||
wait_for_searxng && wait_for_web_search_mcp && wait_for_docs_mcp
|
wait_for_searxng && wait_for_web_search_mcp && wait_for_docs_mcp
|
||||||
}
|
}
|
||||||
@@ -500,18 +627,42 @@ start_locked() {
|
|||||||
if ! docker image inspect "${WEB_SEARCH_IMAGE}" >/dev/null 2>&1 || ! docker image inspect "${DOCS_IMAGE}" >/dev/null 2>&1; then
|
if ! docker image inspect "${WEB_SEARCH_IMAGE}" >/dev/null 2>&1 || ! docker image inspect "${DOCS_IMAGE}" >/dev/null 2>&1; then
|
||||||
cmd_build
|
cmd_build
|
||||||
fi
|
fi
|
||||||
write_docs_sources_file
|
local result=0 rollback_failed=0
|
||||||
|
arm_lifecycle_transaction_traps
|
||||||
local result=0
|
if begin_docs_sources_transaction; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
result=$?
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
return "${result}"
|
||||||
|
fi
|
||||||
|
if write_docs_sources_file; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
result=$?
|
||||||
|
rollback_lifecycle_transaction || rollback_failed=1
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
[[ "${rollback_failed}" -eq 0 ]] || return 1
|
||||||
|
return "${result}"
|
||||||
|
fi
|
||||||
snapshot_shared_services
|
snapshot_shared_services
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_ACTIVE=1
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_REMOVE_NEW=true
|
||||||
compose up -d --no-recreate "${SHARED_SERVICES[@]}" || result=$?
|
compose up -d --no-recreate "${SHARED_SERVICES[@]}" || result=$?
|
||||||
if [[ "${result}" -eq 0 ]] && ! shared_services_ready; then
|
if [[ "${result}" -eq 0 ]] && ! shared_services_ready; then
|
||||||
result=1
|
result=1
|
||||||
fi
|
fi
|
||||||
[[ "${result}" -ne 0 ]] || return 0
|
if [[ "${result}" -eq 0 ]] && discard_docs_sources_transaction; then
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_ACTIVE=0
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
[[ "${result}" -ne 0 ]] || result=1
|
||||||
|
|
||||||
warn "shared service startup failed; restoring prior container states"
|
warn "shared service startup failed; restoring prior docs sources and container states"
|
||||||
restore_shared_service_states true || true
|
rollback_lifecycle_transaction || rollback_failed=1
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
[[ "${rollback_failed}" -eq 0 ]] || return 1
|
||||||
return "${result}"
|
return "${result}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,17 +689,42 @@ cmd_restart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restart_locked() {
|
restart_locked() {
|
||||||
local result=0
|
local result=0 rollback_failed=0
|
||||||
write_docs_sources_file
|
arm_lifecycle_transaction_traps
|
||||||
|
if begin_docs_sources_transaction; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
result=$?
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
return "${result}"
|
||||||
|
fi
|
||||||
|
if write_docs_sources_file; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
result=$?
|
||||||
|
rollback_lifecycle_transaction || rollback_failed=1
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
[[ "${rollback_failed}" -eq 0 ]] || return 1
|
||||||
|
return "${result}"
|
||||||
|
fi
|
||||||
snapshot_shared_services
|
snapshot_shared_services
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_ACTIVE=1
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_REMOVE_NEW=false
|
||||||
compose restart "${SHARED_SERVICES[@]}" || result=$?
|
compose restart "${SHARED_SERVICES[@]}" || result=$?
|
||||||
if [[ "${result}" -eq 0 ]] && ! shared_services_ready; then
|
if [[ "${result}" -eq 0 ]] && ! shared_services_ready; then
|
||||||
result=1
|
result=1
|
||||||
fi
|
fi
|
||||||
[[ "${result}" -ne 0 ]] || return 0
|
if [[ "${result}" -eq 0 ]] && discard_docs_sources_transaction; then
|
||||||
|
LIFECYCLE_STATE_ROLLBACK_ACTIVE=0
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
[[ "${result}" -ne 0 ]] || result=1
|
||||||
|
|
||||||
warn "shared service restart failed; restoring prior container states"
|
warn "shared service restart failed; restoring prior docs sources and container states"
|
||||||
restore_shared_service_states false || true
|
rollback_lifecycle_transaction || rollback_failed=1
|
||||||
|
disarm_lifecycle_transaction_traps
|
||||||
|
[[ "${rollback_failed}" -eq 0 ]] || return 1
|
||||||
return "${result}"
|
return "${result}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ RUN if [ -n "${LLMS_TXT_MCP_VERSION}" ]; then \
|
|||||||
&& rm /tmp/context-kit-docs-constraints.txt
|
&& rm /tmp/context-kit-docs-constraints.txt
|
||||||
|
|
||||||
COPY entrypoint.sh /usr/local/bin/docs-mcp-entrypoint
|
COPY entrypoint.sh /usr/local/bin/docs-mcp-entrypoint
|
||||||
RUN chmod +x /usr/local/bin/docs-mcp-entrypoint
|
RUN chmod 0555 /usr/local/bin/docs-mcp-entrypoint
|
||||||
|
|
||||||
RUN mkdir -p /data /models /etc/context-kit
|
RUN mkdir -p /data /models /etc/context-kit
|
||||||
ENV HF_HOME=/models \
|
ENV HF_HOME=/models \
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
*
|
*
|
||||||
!Dockerfile
|
!Dockerfile
|
||||||
|
!http-entrypoint.mjs
|
||||||
|
!mcp-probe.mjs
|
||||||
!patch-mcp-web-search.mjs
|
!patch-mcp-web-search.mjs
|
||||||
!overrides/
|
!overrides/
|
||||||
!overrides/bing.js
|
!overrides/bing.js
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ RUN npm install -g "@zhafron/mcp-web-search@${MCP_WEB_SEARCH_VERSION}" \
|
|||||||
&& rm /tmp/patch-mcp-web-search.mjs /tmp/context-kit-bing-provider.js \
|
&& rm /tmp/patch-mcp-web-search.mjs /tmp/context-kit-bing-provider.js \
|
||||||
&& npm cache clean --force
|
&& npm cache clean --force
|
||||||
|
|
||||||
|
RUN chmod 0555 /usr/local/lib/context-kit
|
||||||
|
|
||||||
ENV CHROME_PATH=/usr/bin/chromium \
|
ENV CHROME_PATH=/usr/bin/chromium \
|
||||||
DEFAULT_SEARCH_PROVIDER=searxng \
|
DEFAULT_SEARCH_PROVIDER=searxng \
|
||||||
HOME=/tmp \
|
HOME=/tmp \
|
||||||
|
|||||||
@@ -1,6 +1,83 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
cleanup_ephemeral_lifecycle_lock() {
|
||||||
|
local project="${1:-}" lock_dir lock_file owner mode lock_owner
|
||||||
|
[[ "${project}" =~ ^context-kit-release-[0-9]+$ ]] || {
|
||||||
|
printf 'release-check: refusing non-release lifecycle lock project: %s\n' "${project}" >&2
|
||||||
|
return 64
|
||||||
|
}
|
||||||
|
command -v flock >/dev/null 2>&1 || {
|
||||||
|
printf 'release-check: flock is required for lifecycle lock cleanup\n' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
command -v stat >/dev/null 2>&1 || {
|
||||||
|
printf 'release-check: stat is required for lifecycle lock cleanup\n' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
lock_dir="/tmp/context-kit-${project}.lock"
|
||||||
|
lock_file="${lock_dir}/lifecycle"
|
||||||
|
[[ -e "${lock_dir}" || -L "${lock_dir}" ]] || return 0
|
||||||
|
[[ -d "${lock_dir}" && ! -L "${lock_dir}" ]] || {
|
||||||
|
printf 'release-check: refusing unsafe lifecycle lock path: %s\n' "${lock_dir}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if [[ -e "${lock_file}" || -L "${lock_file}" ]]; then
|
||||||
|
[[ -f "${lock_file}" && ! -L "${lock_file}" ]] || {
|
||||||
|
printf 'release-check: refusing unsafe lifecycle lock file: %s\n' "${lock_file}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
exec 9>"${lock_file}" || return 1
|
||||||
|
if ! flock -n 9; then
|
||||||
|
printf 'release-check: lifecycle lock is still held: %s\n' "${lock_dir}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -d "${lock_dir}" && ! -L "${lock_dir}" ]] || {
|
||||||
|
printf 'release-check: lifecycle lock path changed while acquiring it: %s\n' "${lock_dir}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
owner="$(stat -c %u "${lock_dir}")"
|
||||||
|
mode="$(stat -c %a "${lock_dir}")"
|
||||||
|
[[ "${owner}" == "$(id -u)" && "${mode}" == "700" ]] || {
|
||||||
|
printf 'release-check: refusing lifecycle lock with uid %s and mode %s: %s\n' "${owner}" "${mode}" "${lock_dir}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
[[ -f "${lock_file}" && ! -L "${lock_file}" ]] || {
|
||||||
|
printf 'release-check: lifecycle lock file changed while acquiring it: %s\n' "${lock_file}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
lock_owner="$(stat -c %u "${lock_file}")"
|
||||||
|
[[ "${lock_owner}" == "$(id -u)" ]] || {
|
||||||
|
printf 'release-check: refusing lifecycle lock file owned by uid %s: %s\n' "${lock_owner}" "${lock_file}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -f -- "${lock_file}"
|
||||||
|
if ! rmdir -- "${lock_dir}"; then
|
||||||
|
printf 'release-check: lifecycle lock directory contains unexpected entries: %s\n' "${lock_dir}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--cleanup-ephemeral-lock" ]]; then
|
||||||
|
[[ "$#" -eq 2 ]] || {
|
||||||
|
printf 'usage: scripts/release-check --cleanup-ephemeral-lock context-kit-release-PID\n' >&2
|
||||||
|
exit 64
|
||||||
|
}
|
||||||
|
cleanup_ephemeral_lifecycle_lock "$2"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
[[ "$#" -eq 0 ]] || {
|
||||||
|
printf 'usage: scripts/release-check\n' >&2
|
||||||
|
exit 64
|
||||||
|
}
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
cd "${ROOT}"
|
cd "${ROOT}"
|
||||||
|
|
||||||
@@ -44,9 +121,16 @@ printf '%s\n' \
|
|||||||
printf '%s\n' "${CONTEXT_KIT_LOCAL_SOURCE_SMOKE_URL}" > "${local_sources_profile}"
|
printf '%s\n' "${CONTEXT_KIT_LOCAL_SOURCE_SMOKE_URL}" > "${local_sources_profile}"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
local status="$?" lock_status=0
|
||||||
|
trap - EXIT
|
||||||
docker compose -p "${CONTEXT_KIT_COMPOSE_PROJECT}" -f compose.yml down -v --remove-orphans >/dev/null 2>&1 || true
|
docker compose -p "${CONTEXT_KIT_COMPOSE_PROJECT}" -f compose.yml down -v --remove-orphans >/dev/null 2>&1 || true
|
||||||
docker image rm "${CONTEXT_KIT_WEB_SEARCH_IMAGE}" "${CONTEXT_KIT_DOCS_IMAGE}" >/dev/null 2>&1 || true
|
docker image rm "${CONTEXT_KIT_WEB_SEARCH_IMAGE}" "${CONTEXT_KIT_DOCS_IMAGE}" >/dev/null 2>&1 || true
|
||||||
|
cleanup_ephemeral_lifecycle_lock "${CONTEXT_KIT_COMPOSE_PROJECT}" || lock_status=$?
|
||||||
rm -rf "${tmp_dir}"
|
rm -rf "${tmp_dir}"
|
||||||
|
if [[ "${status}" -ne 0 ]]; then
|
||||||
|
exit "${status}"
|
||||||
|
fi
|
||||||
|
exit "${lock_status}"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,27 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
CONTEXT_KIT="${ROOT}/bin/context-kit"
|
CONTEXT_KIT="${ROOT}/bin/context-kit"
|
||||||
|
RELEASE_CHECK="${ROOT}/scripts/release-check"
|
||||||
TEST_ROOT="$(mktemp -d)"
|
TEST_ROOT="$(mktemp -d)"
|
||||||
TEST_PROJECT="context-kit-lifecycle-$$"
|
TEST_PROJECT="context-kit-lifecycle-$$"
|
||||||
LOCK_DIR="/tmp/context-kit-${TEST_PROJECT}.lock"
|
LOCK_DIR="/tmp/context-kit-${TEST_PROJECT}.lock"
|
||||||
|
RELEASE_LOCK_TEST_PROJECT="context-kit-release-$((900000000 + $$))"
|
||||||
|
RELEASE_LOCK_DIR="/tmp/context-kit-${RELEASE_LOCK_TEST_PROJECT}.lock"
|
||||||
|
LOCK_HOLDER_PID=''
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
if [[ -n "${LOCK_HOLDER_PID}" ]]; then
|
||||||
|
kill "${LOCK_HOLDER_PID}" 2>/dev/null || true
|
||||||
|
wait "${LOCK_HOLDER_PID}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
rm -rf "${TEST_ROOT}"
|
rm -rf "${TEST_ROOT}"
|
||||||
if [[ -d "${LOCK_DIR}" && "$(stat -c %u "${LOCK_DIR}")" == "$(id -u)" ]]; then
|
if [[ -d "${LOCK_DIR}" && "$(stat -c %u "${LOCK_DIR}")" == "$(id -u)" ]]; then
|
||||||
rm -rf "${LOCK_DIR}"
|
rm -rf "${LOCK_DIR}"
|
||||||
fi
|
fi
|
||||||
|
if [[ -L "${RELEASE_LOCK_DIR}" ]]; then
|
||||||
|
rm -f "${RELEASE_LOCK_DIR}"
|
||||||
|
elif [[ -d "${RELEASE_LOCK_DIR}" && "$(stat -c %u "${RELEASE_LOCK_DIR}")" == "$(id -u)" ]]; then
|
||||||
|
rm -rf "${RELEASE_LOCK_DIR}"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
@@ -36,6 +49,16 @@ fake_service_for_container() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_docs_sources_restored_before_state_change() {
|
||||||
|
if [[ -n "${FAKE_EXPECT_DOCS_SOURCES:-}" ]]; then
|
||||||
|
cmp -s "${FAKE_EXPECT_DOCS_SOURCES}" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" \
|
||||||
|
|| fail_test "container state restoration ran before prior docs sources content was restored"
|
||||||
|
elif [[ "${FAKE_EXPECT_DOCS_SOURCES_ABSENT:-0}" -eq 1 ]]; then
|
||||||
|
[[ ! -e "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" && ! -L "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" ]] \
|
||||||
|
|| fail_test "container state restoration ran before prior docs sources absence was restored"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
fake_compose() {
|
fake_compose() {
|
||||||
while [[ "$#" -gt 0 ]]; do
|
while [[ "$#" -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -164,12 +187,14 @@ docker() {
|
|||||||
start)
|
start)
|
||||||
local container_id="${!#}" service
|
local container_id="${!#}" service
|
||||||
if service="$(fake_service_for_container "${container_id}" 2>/dev/null)"; then
|
if service="$(fake_service_for_container "${container_id}" 2>/dev/null)"; then
|
||||||
|
assert_docs_sources_restored_before_state_change
|
||||||
touch "${FAKE_DOCKER_STATE}/service.${service}.running"
|
touch "${FAKE_DOCKER_STATE}/service.${service}.running"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
rm)
|
rm)
|
||||||
local container_id="${!#}" service
|
local container_id="${!#}" service
|
||||||
if service="$(fake_service_for_container "${container_id}" 2>/dev/null)"; then
|
if service="$(fake_service_for_container "${container_id}" 2>/dev/null)"; then
|
||||||
|
assert_docs_sources_restored_before_state_change
|
||||||
rm -f "${FAKE_DOCKER_STATE}/service.${service}.id" "${FAKE_DOCKER_STATE}/service.${service}.running"
|
rm -f "${FAKE_DOCKER_STATE}/service.${service}.id" "${FAKE_DOCKER_STATE}/service.${service}.running"
|
||||||
fi
|
fi
|
||||||
rm -f "${FAKE_DOCKER_STATE}/owner.${container_id}"
|
rm -f "${FAKE_DOCKER_STATE}/owner.${container_id}"
|
||||||
@@ -177,6 +202,7 @@ docker() {
|
|||||||
stop)
|
stop)
|
||||||
local container_id="${!#}" service
|
local container_id="${!#}" service
|
||||||
service="$(fake_service_for_container "${container_id}")" || return 1
|
service="$(fake_service_for_container "${container_id}")" || return 1
|
||||||
|
assert_docs_sources_restored_before_state_change
|
||||||
rm -f "${FAKE_DOCKER_STATE}/service.${service}.running"
|
rm -f "${FAKE_DOCKER_STATE}/service.${service}.running"
|
||||||
;;
|
;;
|
||||||
ps)
|
ps)
|
||||||
@@ -214,7 +240,7 @@ curl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sleep() { return 0; }
|
sleep() { return 0; }
|
||||||
export -f fail_test fake_log fake_service_for_container fake_compose docker curl sleep
|
export -f fail_test fake_log fake_service_for_container assert_docs_sources_restored_before_state_change fake_compose docker curl sleep
|
||||||
|
|
||||||
new_case() {
|
new_case() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
@@ -232,7 +258,8 @@ new_case() {
|
|||||||
export CONTEXT_KIT_DOCS_SOURCES=config/sources.default.txt
|
export CONTEXT_KIT_DOCS_SOURCES=config/sources.default.txt
|
||||||
unset CONTEXT_KIT_DOCKER_CIDFILE CONTEXT_KIT_RUNTIME_DIR FAKE_DOCS_UID FAKE_WEB_UID \
|
unset CONTEXT_KIT_DOCKER_CIDFILE CONTEXT_KIT_RUNTIME_DIR FAKE_DOCS_UID FAKE_WEB_UID \
|
||||||
FAKE_REPLACEMENT_REQUIRED FAKE_RESTART_FAIL FAKE_DROP_RUNNING FAKE_SEARXNG_FAIL \
|
FAKE_REPLACEMENT_REQUIRED FAKE_RESTART_FAIL FAKE_DROP_RUNNING FAKE_SEARXNG_FAIL \
|
||||||
FAKE_WEB_SEARCH_FAIL FAKE_DOCS_FAIL FAKE_LEGACY_CONTAINER FAKE_CLIENT_OWNER_MISMATCH
|
FAKE_WEB_SEARCH_FAIL FAKE_DOCS_FAIL FAKE_LEGACY_CONTAINER FAKE_CLIENT_OWNER_MISMATCH \
|
||||||
|
FAKE_EXPECT_DOCS_SOURCES FAKE_EXPECT_DOCS_SOURCES_ABSENT
|
||||||
mkdir -p "${FAKE_DOCKER_STATE}" "${HOME}"
|
mkdir -p "${FAKE_DOCKER_STATE}" "${HOME}"
|
||||||
: > "${FAKE_DOCKER_LOG}"
|
: > "${FAKE_DOCKER_LOG}"
|
||||||
}
|
}
|
||||||
@@ -245,6 +272,62 @@ seed_service() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_no_docs_sources_artifacts() {
|
||||||
|
if compgen -G "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt.lifecycle-backup.*" >/dev/null \
|
||||||
|
|| compgen -G "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt.tmp.*" >/dev/null; then
|
||||||
|
fail_test "docs sources transaction left backup or render artifacts"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -m 700 "${RELEASE_LOCK_DIR}"
|
||||||
|
: > "${RELEASE_LOCK_DIR}/lifecycle"
|
||||||
|
chmod 755 "${RELEASE_LOCK_DIR}"
|
||||||
|
if "${RELEASE_CHECK}" --cleanup-ephemeral-lock "${RELEASE_LOCK_TEST_PROJECT}" >"${TEST_ROOT}/unsafe-lock.out" 2>&1; then
|
||||||
|
fail_test "release lock cleanup accepted an unsafe mode"
|
||||||
|
fi
|
||||||
|
[[ -d "${RELEASE_LOCK_DIR}" ]] || fail_test "release lock cleanup removed an unsafe lock"
|
||||||
|
chmod 700 "${RELEASE_LOCK_DIR}"
|
||||||
|
"${RELEASE_CHECK}" --cleanup-ephemeral-lock "${RELEASE_LOCK_TEST_PROJECT}"
|
||||||
|
|
||||||
|
release_lock_target="${TEST_ROOT}/release-lock-symlink-target"
|
||||||
|
mkdir -m 700 "${release_lock_target}"
|
||||||
|
touch "${release_lock_target}/sentinel"
|
||||||
|
ln -s "${release_lock_target}" "${RELEASE_LOCK_DIR}"
|
||||||
|
if "${RELEASE_CHECK}" --cleanup-ephemeral-lock "${RELEASE_LOCK_TEST_PROJECT}" >"${TEST_ROOT}/symlink-lock.out" 2>&1; then
|
||||||
|
fail_test "release lock cleanup followed a symlink"
|
||||||
|
fi
|
||||||
|
[[ -f "${release_lock_target}/sentinel" ]] || fail_test "release lock cleanup changed a symlink target"
|
||||||
|
rm -f "${RELEASE_LOCK_DIR}"
|
||||||
|
rm -rf "${release_lock_target}"
|
||||||
|
|
||||||
|
mkdir -m 700 "${RELEASE_LOCK_DIR}"
|
||||||
|
: > "${RELEASE_LOCK_DIR}/lifecycle"
|
||||||
|
(
|
||||||
|
flock -x 9
|
||||||
|
touch "${TEST_ROOT}/release-lock-held"
|
||||||
|
/bin/sleep 30
|
||||||
|
) 9>"${RELEASE_LOCK_DIR}/lifecycle" &
|
||||||
|
LOCK_HOLDER_PID=$!
|
||||||
|
for _ in {1..100}; do
|
||||||
|
[[ -f "${TEST_ROOT}/release-lock-held" ]] && break
|
||||||
|
/bin/sleep 0.01
|
||||||
|
done
|
||||||
|
[[ -f "${TEST_ROOT}/release-lock-held" ]] || fail_test "release lock holder did not start"
|
||||||
|
if "${RELEASE_CHECK}" --cleanup-ephemeral-lock "${RELEASE_LOCK_TEST_PROJECT}" >"${TEST_ROOT}/held-lock.out" 2>&1; then
|
||||||
|
fail_test "release lock cleanup removed a held lock"
|
||||||
|
fi
|
||||||
|
grep -F 'still held' "${TEST_ROOT}/held-lock.out" >/dev/null || fail_test "held lock refusal was not explicit"
|
||||||
|
kill "${LOCK_HOLDER_PID}"
|
||||||
|
wait "${LOCK_HOLDER_PID}" 2>/dev/null || true
|
||||||
|
LOCK_HOLDER_PID=''
|
||||||
|
"${RELEASE_CHECK}" --cleanup-ephemeral-lock "${RELEASE_LOCK_TEST_PROJECT}"
|
||||||
|
[[ ! -e "${RELEASE_LOCK_DIR}" && ! -L "${RELEASE_LOCK_DIR}" ]] || fail_test "successful release lock cleanup left the lock path"
|
||||||
|
|
||||||
|
mkdir -m 700 "${RELEASE_LOCK_DIR}"
|
||||||
|
: > "${RELEASE_LOCK_DIR}/lifecycle"
|
||||||
|
"${RELEASE_CHECK}" --cleanup-ephemeral-lock "${RELEASE_LOCK_TEST_PROJECT}"
|
||||||
|
[[ ! -e "${RELEASE_LOCK_DIR}" && ! -L "${RELEASE_LOCK_DIR}" ]] || fail_test "release lock cleanup did not remove its known ephemeral lock"
|
||||||
|
|
||||||
new_case snippets
|
new_case snippets
|
||||||
"${CONTEXT_KIT}" install opencode > "${CASE_ROOT}/opencode.json"
|
"${CONTEXT_KIT}" install opencode > "${CASE_ROOT}/opencode.json"
|
||||||
"${CONTEXT_KIT}" install claude > "${CASE_ROOT}/claude.json"
|
"${CONTEXT_KIT}" install claude > "${CASE_ROOT}/claude.json"
|
||||||
@@ -319,6 +402,12 @@ new_case readiness-failure
|
|||||||
touch "${FAKE_DOCKER_STATE}/network" "${FAKE_DOCKER_STATE}/volume"
|
touch "${FAKE_DOCKER_STATE}/network" "${FAKE_DOCKER_STATE}/volume"
|
||||||
seed_service searxng
|
seed_service searxng
|
||||||
seed_service docs-mcp stopped
|
seed_service docs-mcp stopped
|
||||||
|
mkdir -p "${CONTEXT_KIT_DATA_DIR}"
|
||||||
|
printf 'prior docs sources\nwith exact content\n' > "${CASE_ROOT}/prior-docs-sources.txt"
|
||||||
|
cp "${CASE_ROOT}/prior-docs-sources.txt" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt"
|
||||||
|
printf 'https://new.example.test/llms.txt\n' > "${CASE_ROOT}/new-sources.txt"
|
||||||
|
export CONTEXT_KIT_DOCS_SOURCES="${CASE_ROOT}/new-sources.txt"
|
||||||
|
export FAKE_EXPECT_DOCS_SOURCES="${CASE_ROOT}/prior-docs-sources.txt"
|
||||||
export FAKE_DROP_RUNNING=searxng
|
export FAKE_DROP_RUNNING=searxng
|
||||||
export FAKE_WEB_SEARCH_FAIL=1
|
export FAKE_WEB_SEARCH_FAIL=1
|
||||||
if "${CONTEXT_KIT}" start >"${CASE_ROOT}/start.out" 2>&1; then
|
if "${CONTEXT_KIT}" start >"${CASE_ROOT}/start.out" 2>&1; then
|
||||||
@@ -326,6 +415,9 @@ if "${CONTEXT_KIT}" start >"${CASE_ROOT}/start.out" 2>&1; then
|
|||||||
fi
|
fi
|
||||||
[[ -f "${FAKE_DOCKER_STATE}/service.searxng.running" ]] || fail_test "readiness rollback did not restart prior searxng"
|
[[ -f "${FAKE_DOCKER_STATE}/service.searxng.running" ]] || fail_test "readiness rollback did not restart prior searxng"
|
||||||
[[ ! -f "${FAKE_DOCKER_STATE}/service.docs-mcp.running" ]] || fail_test "readiness rollback did not restore prior stopped docs state"
|
[[ ! -f "${FAKE_DOCKER_STATE}/service.docs-mcp.running" ]] || fail_test "readiness rollback did not restore prior stopped docs state"
|
||||||
|
[[ "$(<"${FAKE_DOCKER_STATE}/service.docs-mcp.id")" == cid-docs-mcp ]] || fail_test "readiness rollback changed the prior docs container ID"
|
||||||
|
cmp -s "${CASE_ROOT}/prior-docs-sources.txt" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" || fail_test "readiness rollback did not restore prior docs sources content"
|
||||||
|
assert_no_docs_sources_artifacts
|
||||||
[[ ! -f "${FAKE_DOCKER_STATE}/service.web-search-mcp.id" ]] || fail_test "readiness rollback left its new web container"
|
[[ ! -f "${FAKE_DOCKER_STATE}/service.web-search-mcp.id" ]] || fail_test "readiness rollback left its new web container"
|
||||||
[[ -f "${FAKE_DOCKER_STATE}/network" && -f "${FAKE_DOCKER_STATE}/volume" ]] || fail_test "readiness rollback removed origin resources"
|
[[ -f "${FAKE_DOCKER_STATE}/network" && -f "${FAKE_DOCKER_STATE}/volume" ]] || fail_test "readiness rollback removed origin resources"
|
||||||
|
|
||||||
@@ -338,20 +430,41 @@ export CONTEXT_KIT_DOCS_SOURCES="${CASE_ROOT}/sources.txt"
|
|||||||
"${CONTEXT_KIT}" restart
|
"${CONTEXT_KIT}" restart
|
||||||
grep -F 'https://example.test/llms.txt' "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" >/dev/null \
|
grep -F 'https://example.test/llms.txt' "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" >/dev/null \
|
||||||
|| fail_test "restart did not regenerate the bind-mounted docs source list"
|
|| fail_test "restart did not regenerate the bind-mounted docs source list"
|
||||||
|
assert_no_docs_sources_artifacts
|
||||||
|
|
||||||
new_case restart-failure
|
new_case restart-failure
|
||||||
seed_service searxng stopped
|
seed_service searxng stopped
|
||||||
seed_service web-search-mcp
|
seed_service web-search-mcp
|
||||||
seed_service docs-mcp
|
seed_service docs-mcp stopped
|
||||||
export FAKE_DROP_RUNNING=docs-mcp
|
mkdir -p "${CONTEXT_KIT_DATA_DIR}"
|
||||||
export FAKE_RESTART_FAIL=1
|
printf 'prior restart sources\n' > "${CASE_ROOT}/prior-docs-sources.txt"
|
||||||
|
cp "${CASE_ROOT}/prior-docs-sources.txt" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt"
|
||||||
|
printf 'https://restart.example.test/llms.txt\n' > "${CASE_ROOT}/new-sources.txt"
|
||||||
|
export CONTEXT_KIT_DOCS_SOURCES="${CASE_ROOT}/new-sources.txt"
|
||||||
|
export FAKE_EXPECT_DOCS_SOURCES="${CASE_ROOT}/prior-docs-sources.txt"
|
||||||
|
export FAKE_DOCS_FAIL=1
|
||||||
if "${CONTEXT_KIT}" restart >"${CASE_ROOT}/restart.out" 2>&1; then
|
if "${CONTEXT_KIT}" restart >"${CASE_ROOT}/restart.out" 2>&1; then
|
||||||
fail_test "restart failure unexpectedly succeeded"
|
fail_test "restart failure unexpectedly succeeded"
|
||||||
fi
|
fi
|
||||||
[[ -f "${FAKE_DOCKER_STATE}/service.docs-mcp.running" ]] || fail_test "restart rollback left prior docs down"
|
[[ ! -f "${FAKE_DOCKER_STATE}/service.docs-mcp.running" ]] || fail_test "restart rollback did not restore prior stopped docs state"
|
||||||
|
[[ "$(<"${FAKE_DOCKER_STATE}/service.docs-mcp.id")" == cid-docs-mcp ]] || fail_test "restart rollback changed the prior docs container ID"
|
||||||
[[ ! -f "${FAKE_DOCKER_STATE}/service.searxng.running" ]] || fail_test "restart rollback did not restore prior stopped SearXNG state"
|
[[ ! -f "${FAKE_DOCKER_STATE}/service.searxng.running" ]] || fail_test "restart rollback did not restore prior stopped SearXNG state"
|
||||||
|
cmp -s "${CASE_ROOT}/prior-docs-sources.txt" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" || fail_test "restart rollback did not restore prior docs sources content"
|
||||||
|
assert_no_docs_sources_artifacts
|
||||||
grep -F 'docker rm' "${FAKE_DOCKER_LOG}" >/dev/null && fail_test "restart rollback removed a shared container"
|
grep -F 'docker rm' "${FAKE_DOCKER_LOG}" >/dev/null && fail_test "restart rollback removed a shared container"
|
||||||
|
|
||||||
|
new_case render-error
|
||||||
|
mkdir -p "${CONTEXT_KIT_DATA_DIR}"
|
||||||
|
printf 'prior render-error sources\n' > "${CASE_ROOT}/prior-docs-sources.txt"
|
||||||
|
cp "${CASE_ROOT}/prior-docs-sources.txt" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt"
|
||||||
|
export CONTEXT_KIT_DOCS_SOURCES="${CASE_ROOT}/missing-sources.txt"
|
||||||
|
if "${CONTEXT_KIT}" start >"${CASE_ROOT}/start.out" 2>&1; then
|
||||||
|
fail_test "docs sources render error unexpectedly succeeded"
|
||||||
|
fi
|
||||||
|
cmp -s "${CASE_ROOT}/prior-docs-sources.txt" "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" || fail_test "render error did not restore prior docs sources"
|
||||||
|
assert_no_docs_sources_artifacts
|
||||||
|
grep -F ' up ' "${FAKE_DOCKER_LOG}" >/dev/null && fail_test "render error reached Compose startup"
|
||||||
|
|
||||||
new_case cross-user
|
new_case cross-user
|
||||||
seed_service docs-mcp
|
seed_service docs-mcp
|
||||||
export FAKE_DOCS_UID="$(( $(id -u) + 1 ))"
|
export FAKE_DOCS_UID="$(( $(id -u) + 1 ))"
|
||||||
@@ -363,12 +476,18 @@ grep -F ' up ' "${FAKE_DOCKER_LOG}" >/dev/null && fail_test "cross-user rejectio
|
|||||||
|
|
||||||
new_case bounded-failure
|
new_case bounded-failure
|
||||||
export FAKE_WEB_SEARCH_FAIL=1
|
export FAKE_WEB_SEARCH_FAIL=1
|
||||||
|
export FAKE_EXPECT_DOCS_SOURCES_ABSENT=1
|
||||||
|
[[ ! -e "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" && ! -L "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" ]] \
|
||||||
|
|| fail_test "prior-absence case unexpectedly began with docs sources"
|
||||||
if "${CONTEXT_KIT}" start >"${CASE_ROOT}/start.out" 2>&1; then
|
if "${CONTEXT_KIT}" start >"${CASE_ROOT}/start.out" 2>&1; then
|
||||||
fail_test "fresh readiness failure unexpectedly succeeded"
|
fail_test "fresh readiness failure unexpectedly succeeded"
|
||||||
fi
|
fi
|
||||||
for service in searxng web-search-mcp docs-mcp; do
|
for service in searxng web-search-mcp docs-mcp; do
|
||||||
[[ ! -f "${FAKE_DOCKER_STATE}/service.${service}.id" ]] || fail_test "fresh failure left ${service}"
|
[[ ! -f "${FAKE_DOCKER_STATE}/service.${service}.id" ]] || fail_test "fresh failure left ${service}"
|
||||||
done
|
done
|
||||||
|
[[ ! -e "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" && ! -L "${CONTEXT_KIT_DATA_DIR}/docs-sources.txt" ]] \
|
||||||
|
|| fail_test "fresh readiness rollback did not restore prior docs sources absence"
|
||||||
|
assert_no_docs_sources_artifacts
|
||||||
[[ -f "${FAKE_DOCKER_STATE}/network" && -f "${FAKE_DOCKER_STATE}/volume" ]] || fail_test "bounded shared resources were destructively removed"
|
[[ -f "${FAKE_DOCKER_STATE}/network" && -f "${FAKE_DOCKER_STATE}/volume" ]] || fail_test "bounded shared resources were destructively removed"
|
||||||
|
|
||||||
new_case legacy-status
|
new_case legacy-status
|
||||||
|
|||||||
Reference in New Issue
Block a user