fix(preprocess): enforce local vector startup chain

This commit is contained in:
2026-07-12 07:54:09 +02:00
parent e40a9d9a56
commit 72bc50ab2e
6 changed files with 84 additions and 34 deletions
@@ -33,3 +33,17 @@ The cleanup boundary now begins immediately after snapshot materialization. Resu
validation and `JobSpec` construction are guarded by the same release routine as `run_job`, so validation and `JobSpec` construction are guarded by the same release routine as `run_job`, so
corrupt/mismatched resume state or constructor failure clears the pipeline holder, removes the corrupt/mismatched resume state or constructor failure clears the pipeline holder, removes the
private directory, and restores the snapshot registry to its prior state before propagating. private directory, and restores the snapshot registry to its prior state before propagating.
## Shipped preprocessing startup contract
Local-vector preprocessing now uses a dedicated Compose override. Both one-shot jobs depend on a
successfully completed `vector-migrate`, whose transitive chain waits for database health and role
reconciliation. The generic preprocessing overlay remains independently renderable and contains no
local-vector services or password secrets. README commands include the local override and build the
job image before running.
The real clean-project smoke no longer injects dependencies or manually starts, reconciles, or
migrates PostgreSQL. Its first shipped `compose run preprocess-evidence` demonstrably creates the
database, waits for health, runs reconciliation and migration, then runs the Evidence job. Unchanged
rerun, changed-source publish, DWH preprocessing, ACTIVE verification, and injected-failure cleanup
all pass through the same shipped dependency path.
+4 -14
View File
@@ -62,23 +62,13 @@ runtime):
```sh ```sh
docker compose -f compose.yaml -f deploy/compose.local-vector.yaml \ docker compose -f compose.yaml -f deploy/compose.local-vector.yaml \
-f deploy/compose.preprocess.yaml -f deploy/compose.preprocess-local-vector.yaml \ -f deploy/compose.preprocess.yaml --profile local-vector --profile preprocess \
--profile local-vector --profile preprocess build preprocess-evidence run --rm preprocess-evidence
docker compose -f compose.yaml -f deploy/compose.local-vector.yaml \ docker compose -f compose.yaml -f deploy/compose.local-vector.yaml \
-f deploy/compose.preprocess.yaml -f deploy/compose.preprocess-local-vector.yaml \ -f deploy/compose.preprocess.yaml --profile local-vector --profile preprocess \
--profile local-vector --profile preprocess run --rm preprocess-evidence run --rm preprocess-dwh
docker compose -f compose.yaml -f deploy/compose.local-vector.yaml \
-f deploy/compose.preprocess.yaml -f deploy/compose.preprocess-local-vector.yaml \
--profile local-vector --profile preprocess build preprocess-dwh
docker compose -f compose.yaml -f deploy/compose.local-vector.yaml \
-f deploy/compose.preprocess.yaml -f deploy/compose.preprocess-local-vector.yaml \
--profile local-vector --profile preprocess run --rm preprocess-dwh
``` ```
The local preprocessing override makes each job wait for the vector database health check,
role reconciliation, and a successful migration. These commands are safe on a clean Compose
project; no separate database startup or migration command is required.
S3 Evidence uses the optional `tht[s3]` dependency and canonical `s3://bucket/key` provenance. S3 Evidence uses the optional `tht[s3]` dependency and canonical `s3://bucket/key` provenance.
AWS endpoints are used when no custom URL is supplied. Every custom endpoint is an explicit egress AWS endpoints are used when no custom URL is supplied. Every custom endpoint is an explicit egress
trust-boundary opt-in and uses path-style addressing; private and HTTP endpoints require additional trust-boundary opt-in and uses path-style addressing; private and HTTP endpoints require additional
@@ -0,0 +1,21 @@
services:
preprocess-evidence:
environment:
THT_VECTOR_READER_PASSWORD_FILE: /run/secrets/vector_reader_password
THT_VECTOR_WRITER_PASSWORD_FILE: /run/secrets/vector_writer_password
secrets: [vector_reader_password, vector_writer_password]
depends_on:
vector-migrate: {condition: service_completed_successfully}
preprocess-dwh:
environment:
THT_VECTOR_READER_PASSWORD_FILE: /run/secrets/vector_reader_password
secrets: [vector_reader_password]
depends_on:
vector-migrate: {condition: service_completed_successfully}
secrets:
vector_reader_password:
file: ${THT_VECTOR_READER_PASSWORD_SECRET_FILE:?set THT_VECTOR_READER_PASSWORD_SECRET_FILE}
vector_writer_password:
file: ${THT_VECTOR_WRITER_PASSWORD_SECRET_FILE:?set THT_VECTOR_WRITER_PASSWORD_SECRET_FILE}
-11
View File
@@ -10,9 +10,6 @@ services:
environment: environment:
THT_DATA_ROOT: /data THT_DATA_ROOT: /data
THT_OLLAMA_URL: "${THT_OLLAMA_URL:-http://host.docker.internal:11434}" THT_OLLAMA_URL: "${THT_OLLAMA_URL:-http://host.docker.internal:11434}"
THT_VECTOR_READER_PASSWORD_FILE: /run/secrets/vector_reader_password
THT_VECTOR_WRITER_PASSWORD_FILE: /run/secrets/vector_writer_password
secrets: [vector_reader_password, vector_writer_password]
volumes: volumes:
- thoth_data:/data - thoth_data:/data
- ./deploy/workspaces:/app/harness/workspaces:ro - ./deploy/workspaces:/app/harness/workspaces:ro
@@ -28,15 +25,7 @@ services:
command: ["mkdir -p /data/workspaces/preprocess-dwh && exec /app/docker/core-entrypoint.sh preprocess dwh --steps introspect --json -c /app/harness/workspaces/preprocess-dwh.yaml"] command: ["mkdir -p /data/workspaces/preprocess-dwh && exec /app/docker/core-entrypoint.sh preprocess dwh --steps introspect --json -c /app/harness/workspaces/preprocess-dwh.yaml"]
environment: environment:
THT_DATA_ROOT: /data THT_DATA_ROOT: /data
THT_VECTOR_READER_PASSWORD_FILE: /run/secrets/vector_reader_password
secrets: [vector_reader_password]
volumes: volumes:
- thoth_data:/data - thoth_data:/data
- ./deploy/workspaces:/app/harness/workspaces:ro - ./deploy/workspaces:/app/harness/workspaces:ro
restart: "no" restart: "no"
secrets:
vector_reader_password:
file: ${THT_VECTOR_READER_PASSWORD_SECRET_FILE:?set THT_VECTOR_READER_PASSWORD_SECRET_FILE}
vector_writer_password:
file: ${THT_VECTOR_WRITER_PASSWORD_SECRET_FILE:?set THT_VECTOR_WRITER_PASSWORD_SECRET_FILE}
+2 -9
View File
@@ -79,18 +79,11 @@ services:
volumes: volumes:
- $tmp/source:/data/source:ro - $tmp/source:/data/source:ro
depends_on: depends_on:
vector-migrate: {condition: service_completed_successfully}
mock-embeddings: {condition: service_started} mock-embeddings: {condition: service_started}
preprocess-dwh:
depends_on:
vector-migrate: {condition: service_completed_successfully}
YAML YAML
compose="docker compose -f compose.yaml -f deploy/compose.local-vector.yaml -f deploy/compose.preprocess.yaml -f $tmp/smoke.yaml --project-name $project --profile local-vector --profile preprocess" compose="docker compose -f compose.yaml -f deploy/compose.local-vector.yaml -f deploy/compose.preprocess.yaml -f deploy/compose.preprocess-local-vector.yaml -f $tmp/smoke.yaml --project-name $project --profile local-vector --profile preprocess"
$compose build preprocess-evidence $compose build preprocess-evidence
$compose up -d vector-db mock-embeddings
$compose run --rm vector-reconcile >/dev/null
$compose run --rm --no-deps vector-migrate >/dev/null
if [ "${PREPROCESS_SMOKE_INJECT_FAILURE:-0}" = "1" ]; then if [ "${PREPROCESS_SMOKE_INJECT_FAILURE:-0}" = "1" ]; then
sh -c 'exit 97' sh -c 'exit 97'
fi fi
@@ -98,7 +91,7 @@ generation_count() {
$compose run --rm --no-deps --entrypoint /opt/venv/bin/python preprocess-evidence -c \ $compose run --rm --no-deps --entrypoint /opt/venv/bin/python preprocess-evidence -c \
'import pathlib,re; root=pathlib.Path("/data/workspaces/preprocess-evidence/corpus"); print(sum(1 for p in root.iterdir() if p.is_dir() and re.fullmatch(r"gen-[0-9a-f]{32}",p.name)) if root.exists() else 0)' 'import pathlib,re; root=pathlib.Path("/data/workspaces/preprocess-evidence/corpus"); print(sum(1 for p in root.iterdir() if p.is_dir() and re.fullmatch(r"gen-[0-9a-f]{32}",p.name)) if root.exists() else 0)'
} }
before=$(generation_count) before=0
first=$($compose run --rm preprocess-evidence) first=$($compose run --rm preprocess-evidence)
after_first=$(generation_count) after_first=$(generation_count)
second=$($compose run --rm preprocess-evidence) second=$($compose run --rm preprocess-evidence)
+43
View File
@@ -0,0 +1,43 @@
#!/bin/sh
set -eu
cd "$(dirname "$0")/.."
export THT_VECTOR_BOOTSTRAP_PASSWORD_SECRET_FILE=${THT_VECTOR_BOOTSTRAP_PASSWORD_SECRET_FILE:-/tmp/vector-bootstrap}
export THT_VECTOR_MIGRATOR_PASSWORD_SECRET_FILE=${THT_VECTOR_MIGRATOR_PASSWORD_SECRET_FILE:-/tmp/vector-migrator}
export THT_VECTOR_READER_PASSWORD_SECRET_FILE=${THT_VECTOR_READER_PASSWORD_SECRET_FILE:-/tmp/vector-reader}
export THT_VECTOR_WRITER_PASSWORD_SECRET_FILE=${THT_VECTOR_WRITER_PASSWORD_SECRET_FILE:-/tmp/vector-writer}
local_files="-f compose.yaml -f deploy/compose.local-vector.yaml -f deploy/compose.preprocess.yaml -f deploy/compose.preprocess-local-vector.yaml"
local_json=$(docker compose $local_files --profile local-vector --profile preprocess config --format json)
printf '%s' "$local_json" | python3 -c '
import json, sys
config = json.load(sys.stdin)
services = config["services"]
for name in ("preprocess-evidence", "preprocess-dwh"):
dependency = services[name].get("depends_on", {}).get("vector-migrate")
assert dependency is not None, f"{name} does not depend on vector-migrate"
assert dependency["condition"] == "service_completed_successfully", dependency
'
external_json=$(docker compose \
-f compose.yaml -f deploy/compose.preprocess.yaml \
--profile preprocess config --format json)
printf '%s' "$external_json" | python3 -c '
import json, sys
config = json.load(sys.stdin)
services = config["services"]
assert "vector-db" not in services
assert "vector-migrate" not in services
assert "vector-reconcile" not in services
for name in ("preprocess-evidence", "preprocess-dwh"):
service = services[name]
assert "depends_on" not in service
assert not service.get("secrets"), service.get("secrets")
'
echo "preprocess compose config: ok"