fix: tighten internal semantic compose contracts

This commit is contained in:
2026-08-08 18:47:22 +02:00
parent 320d9ea74e
commit 8f4ec1e1a3
7 changed files with 106 additions and 6 deletions
+5
View File
@@ -40,6 +40,8 @@ for (const service of [qdrant, embedding, modelInit]) {
}
if ((qdrant.expose || []).join(",") !== "6333") throw new Error("qdrant must expose only 6333");
if ((embedding.expose || []).join(",") !== "11434") throw new Error("embedding must expose only 11434");
if (!qdrant.healthcheck) throw new Error("qdrant must define a healthcheck");
if (!embedding.healthcheck) throw new Error("embedding must define a healthcheck");
if (qdrant.image !== "qdrant/qdrant:v1.18.2@sha256:75eab8c4ba42096724fdcfde8b4de0b5713d529dde32f285a1f86fdcb2c9e50c") {
throw new Error("qdrant image must be pinned by version and digest");
}
@@ -72,6 +74,9 @@ if (depends.qdrant?.condition !== "service_healthy") {
if (depends["embedding-model-init"]?.condition !== "service_completed_successfully") {
throw new Error("core must wait for embedding-model-init success");
}
if (modelInit.depends_on?.embedding?.condition !== "service_healthy") {
throw new Error("embedding-model-init must wait for embedding health");
}
if (JSON.stringify(embedding).includes('"devices"')) {
throw new Error("base embedding service must stay CPU-only");
}
+3
View File
@@ -19,6 +19,7 @@ TASK13_ROOT="$root"
TASK13_TMP="$fixture"
TASK13_RUN_ID="fixture-$profile"
TASK13_PROJECT="thothii-task13-$profile"
TASK13_PROFILE="$profile"
TASK13_CORE_IMAGE="task13-core-$profile:fixture"
TASK13_FRONTEND_IMAGE="task13-frontend-$profile:fixture"
TASK13_SECRET_VALUE="task13-runtime-secret-$profile"
@@ -36,6 +37,7 @@ TASK13_PI_SETTINGS="$fixture/settings.json"
TASK13_LLM_SERVER="$fixture/fake-llm.mjs"
TASK13_LLM_CONTAINER="$TASK13_PROJECT-llm"
TASK13_REMOTE="$fixture/remote.git"
TASK13_CURRENT_IMAGE_OVERRIDE="$fixture/current-image.yaml"
mkdir -p "$TASK13_REMOTE"
workspace="$fixture/task13-smoke.yaml"
@@ -94,6 +96,7 @@ fi
rendered="$fixture/rendered.json"
docker compose --project-name "$TASK13_PROJECT" --project-directory "$root" \
--env-file "$TASK13_ENV_FILE" "${compose_files[@]}" config --format json >"$rendered"
task13_assert_rendered_contract
tsx_loader="$root/backend/node_modules/tsx/dist/loader.mjs"
checker=(node --import "$tsx_loader" "$root/scripts/task13-runtime-fixture-check.ts")
[[ -f "$tsx_loader" ]] || {
+9
View File
@@ -75,12 +75,17 @@ for (const serviceName of ["qdrant", "embedding", "embedding-model-init"]) {
}
if ((config.services.qdrant.expose || []).join(",") !== "6333") throw new Error("qdrant must expose only 6333");
if ((config.services.embedding.expose || []).join(",") !== "11434") throw new Error("embedding must expose only 11434");
if (!config.services.qdrant.healthcheck) throw new Error("qdrant must define a healthcheck");
if (!config.services.embedding.healthcheck) throw new Error("embedding must define a healthcheck");
if (config.services.core.depends_on?.qdrant?.condition !== "service_healthy") {
throw new Error("core must wait for qdrant health");
}
if (config.services.core.depends_on?.["embedding-model-init"]?.condition !== "service_completed_successfully") {
throw new Error("core must wait for embedding-model-init success");
}
if (config.services["embedding-model-init"].depends_on?.embedding?.condition !== "service_healthy") {
throw new Error("embedding-model-init must wait for embedding health");
}
if (JSON.stringify(config.services.embedding).includes('"devices"')) {
throw new Error("base embedding service must stay CPU-only");
}
@@ -190,6 +195,10 @@ if (config.services.core.depends_on?.qdrant?.condition !== "service_healthy") {
if (config.services.core.depends_on?.["embedding-model-init"]?.condition !== "service_completed_successfully") {
throw new Error("core must wait for embedding-model-init success");
}
if (!config.services.embedding.healthcheck) throw new Error("embedding must define a healthcheck");
if (config.services["embedding-model-init"].depends_on?.embedding?.condition !== "service_healthy") {
throw new Error("embedding-model-init must wait for embedding health");
}
if (/docker\.sock|\/var\/run\/docker|docker[-_]?daemon/i.test(JSON.stringify(config.services))) {
throw new Error("Compose must not mount the Docker socket or daemon");
}
+8 -2
View File
@@ -518,7 +518,8 @@ task13_build_thothctl() {
task13_assert_rendered_contract() {
local services rendered
services="$(task13_compose config --services | sort)"
[[ "$services" == $'core\nfrontend' ]] || task13_fail "rendered stack is not exactly core and frontend"
[[ "$services" == $'core\nembedding\nembedding-model-init\nfrontend\nqdrant' ]] \
|| task13_fail "rendered stack is not the mandatory internal semantic topology"
rendered="$TASK13_TMP/rendered-compose.yaml"
task13_compose config >"$rendered"
if grep -Eqi 'docker\.sock|/var/run/docker' "$rendered"; then
@@ -527,9 +528,14 @@ task13_assert_rendered_contract() {
if grep -Fq "$TASK13_SECRET_VALUE" "$rendered"; then
task13_fail "rendered Compose exposed the fixture secret"
fi
for endpoint in THT_DWH_REST_URL THT_VEC_REST_URL THT_OLLAMA_URL THT_LLM_URL; do
for endpoint in THT_DWH_REST_URL THT_LLM_URL \
THT_INTERNAL_QDRANT_URL THT_INTERNAL_EMBEDDING_URL \
THT_INTERNAL_EMBEDDING_MODEL THT_INTERNAL_EMBEDDING_DIMENSIONS; do
grep -Fq "$endpoint" "$rendered" || task13_fail "rendered Compose lacks $endpoint"
done
if grep -Eq 'THT_VEC_REST_URL|THT_VEC_WRITE_REST_URL|THT_OLLAMA_URL' "$rendered"; then
task13_fail "rendered Compose still exposes retired external semantic bindings"
fi
}
task13_start_stack() {