275 lines
9 KiB
Bash
Executable file
275 lines
9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
source_container="${SOURCE_CONTAINER:-teleo-pg}"
|
|
source_db="${SOURCE_DB:-teleo}"
|
|
service="${LEO_SERVICE:-leoclean-gateway.service}"
|
|
image="${POSTGRES_CANARY_IMAGE:-postgres:16}"
|
|
source_tier="${SOURCE_TIER:-live-readonly}"
|
|
normalization_json=""
|
|
output="${repo_root}/docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.json"
|
|
lifecycle_receipt_output=""
|
|
rollback_sql_output=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--normalization-json)
|
|
normalization_json="$2"
|
|
shift 2
|
|
;;
|
|
--output)
|
|
output="$2"
|
|
shift 2
|
|
;;
|
|
--lifecycle-receipt-output)
|
|
lifecycle_receipt_output="$2"
|
|
shift 2
|
|
;;
|
|
--rollback-sql-output)
|
|
rollback_sql_output="$2"
|
|
shift 2
|
|
;;
|
|
--source-container)
|
|
source_container="$2"
|
|
shift 2
|
|
;;
|
|
--source-db)
|
|
source_db="$2"
|
|
shift 2
|
|
;;
|
|
--service)
|
|
service="$2"
|
|
shift 2
|
|
;;
|
|
--source-tier)
|
|
source_tier="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
echo "unknown argument: $1" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$source_tier" != "isolated-local" && "$source_tier" != "live-readonly" ]]; then
|
|
echo "--source-tier must be isolated-local or live-readonly" >&2
|
|
exit 2
|
|
fi
|
|
|
|
source_network_mode="$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$source_container")"
|
|
source_canary_label="$(
|
|
docker inspect -f '{{index .Config.Labels "livingip.canary"}}' "$source_container"
|
|
)"
|
|
if [[ "$source_tier" == "isolated-local" ]]; then
|
|
if [[ "$source_network_mode" != "none" || "$source_canary_label" != "working-leo-approved-source" ]]; then
|
|
echo "isolated-local source must be networkless and labeled livingip.canary=working-leo-approved-source" >&2
|
|
exit 2
|
|
fi
|
|
fi
|
|
|
|
container="working-leo-approve-claim-$RANDOM-$$"
|
|
workdir="/tmp/${container}"
|
|
apply_password="isolated-apply-${RANDOM}-${RANDOM}"
|
|
review_password="isolated-review-${RANDOM}-${RANDOM}"
|
|
|
|
cleanup() {
|
|
docker rm -f "$container" >/dev/null 2>&1 || true
|
|
rm -rf "$workdir"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
source_counts() {
|
|
docker exec "$source_container" psql -U postgres -d "$source_db" -Atq \
|
|
-v ON_ERROR_STOP=1 -c "begin transaction read only;
|
|
select jsonb_build_object(
|
|
'public.claims',(select count(*) from public.claims),
|
|
'public.sources',(select count(*) from public.sources),
|
|
'public.claim_evidence',(select count(*) from public.claim_evidence),
|
|
'public.claim_edges',(select count(*) from public.claim_edges),
|
|
'public.reasoning_tools',(select count(*) from public.reasoning_tools),
|
|
'kb_stage.kb_proposals',(select count(*) from kb_stage.kb_proposals)
|
|
)::text;
|
|
rollback;"
|
|
}
|
|
|
|
service_state() {
|
|
if ! command -v systemctl >/dev/null 2>&1; then
|
|
printf 'Available=false\nReason=systemctl_unavailable\n'
|
|
return 0
|
|
fi
|
|
local output
|
|
if output="$(systemctl show "$service" --no-pager \
|
|
-p ActiveState -p SubState -p MainPID -p NRestarts -p ExecMainStartTimestamp 2>&1)"; then
|
|
printf 'Available=true\n%s\n' "$output"
|
|
else
|
|
printf 'Available=false\nReason=service_readback_failed\n'
|
|
fi
|
|
}
|
|
|
|
mkdir -p "$workdir" "$(dirname "$output")"
|
|
if [[ -n "$lifecycle_receipt_output" ]]; then
|
|
mkdir -p "$(dirname "$lifecycle_receipt_output")"
|
|
fi
|
|
if [[ -n "$rollback_sql_output" ]]; then
|
|
mkdir -p "$(dirname "$rollback_sql_output")"
|
|
fi
|
|
source_counts_before="$(source_counts)"
|
|
service_before="$(service_state)"
|
|
|
|
docker run -d --name "$container" \
|
|
--network none \
|
|
--label livingip.canary=proposal-apply-lifecycle \
|
|
--label "livingip.canary.instance=${container}" \
|
|
--tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m \
|
|
-e POSTGRES_PASSWORD="isolated-postgres-${RANDOM}-${RANDOM}" \
|
|
-e POSTGRES_DB=teleo \
|
|
"$image" >/dev/null
|
|
|
|
ready_streak=0
|
|
for _ in $(seq 1 80); do
|
|
if docker exec "$container" pg_isready -U postgres -d teleo >/dev/null 2>&1; then
|
|
ready_streak=$((ready_streak + 1))
|
|
if [[ "$ready_streak" -ge 2 ]]; then
|
|
break
|
|
fi
|
|
else
|
|
ready_streak=0
|
|
fi
|
|
sleep 0.25
|
|
done
|
|
if [[ "$ready_streak" -lt 2 ]]; then
|
|
echo "disposable Postgres did not become stably ready" >&2
|
|
exit 1
|
|
fi
|
|
container_network_mode="$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$container")"
|
|
container_mounts_json="$(docker inspect -f '{{json .Mounts}}' "$container")"
|
|
container_canary_label="$(
|
|
docker inspect -f '{{index .Config.Labels "livingip.canary"}}' "$container"
|
|
)"
|
|
container_instance_label="$(
|
|
docker inspect -f '{{index .Config.Labels "livingip.canary.instance"}}' "$container"
|
|
)"
|
|
|
|
docker exec "$source_container" pg_dump -U postgres -d "$source_db" \
|
|
--schema-only --no-owner --no-privileges >"$workdir/schema.sql"
|
|
docker exec "$source_container" pg_dump -U postgres -d "$source_db" \
|
|
--data-only --no-owner --no-privileges \
|
|
--table=public.agents --table=public.claims >"$workdir/reference-data.sql"
|
|
docker cp "$workdir/schema.sql" "$container:/tmp/schema.sql"
|
|
docker cp "$workdir/reference-data.sql" "$container:/tmp/reference-data.sql"
|
|
|
|
docker exec -i "$container" psql -U postgres -d postgres -v ON_ERROR_STOP=1 -q <<SQL
|
|
create role kb_apply login noinherit nosuperuser nocreatedb nocreaterole
|
|
noreplication nobypassrls password '${apply_password}';
|
|
create role kb_review login noinherit nosuperuser nocreatedb nocreaterole
|
|
noreplication nobypassrls password '${review_password}';
|
|
create role kb_gate_owner nologin noinherit nosuperuser nocreatedb nocreaterole
|
|
noreplication nobypassrls;
|
|
SQL
|
|
docker exec "$container" psql -U postgres -d teleo -v ON_ERROR_STOP=1 -q \
|
|
-f /tmp/schema.sql
|
|
docker exec "$container" psql -U postgres -d teleo -v ON_ERROR_STOP=1 -q \
|
|
-f /tmp/reference-data.sql
|
|
docker exec "$container" psql -U postgres -d teleo -v ON_ERROR_STOP=1 -q \
|
|
-c "insert into public.agents (id, handle, kind)
|
|
values ('99999999-9999-9999-9999-999999999999', 'm3ta', 'human')
|
|
on conflict (handle) do nothing"
|
|
|
|
cat >"$workdir/kb-apply.env" <<EOF
|
|
KB_APPLY_PASSWORD=${apply_password}
|
|
EOF
|
|
cat >"$workdir/kb-review.env" <<EOF
|
|
KB_REVIEW_PASSWORD=${review_password}
|
|
EOF
|
|
chmod 600 "$workdir/kb-apply.env" "$workdir/kb-review.env"
|
|
|
|
normalization_args=()
|
|
if [[ -n "$normalization_json" ]]; then
|
|
normalization_args=(--normalization-json "$normalization_json")
|
|
fi
|
|
artifact_args=()
|
|
if [[ -n "$lifecycle_receipt_output" ]]; then
|
|
artifact_args+=(--lifecycle-receipt-output "$lifecycle_receipt_output")
|
|
fi
|
|
if [[ -n "$rollback_sql_output" ]]; then
|
|
artifact_args+=(--rollback-sql-output "$rollback_sql_output")
|
|
fi
|
|
|
|
set +e
|
|
python3 "$repo_root/scripts/run_approve_claim_clone_canary.py" \
|
|
--container "$container" \
|
|
--source-db teleo \
|
|
--database-prefix teleo_approve_claim_isolated \
|
|
--service "$service" \
|
|
--approve-script "$repo_root/scripts/approve_proposal.py" \
|
|
--apply-script "$repo_root/scripts/apply_proposal.py" \
|
|
--prereqs "$repo_root/scripts/kb_apply_prereqs.sql" \
|
|
--review-secrets-file "$workdir/kb-review.env" \
|
|
--secrets-file "$workdir/kb-apply.env" \
|
|
"${normalization_args[@]}" \
|
|
"${artifact_args[@]}" \
|
|
--output "$output" \
|
|
>"$workdir/stdout.json" 2>"$workdir/stderr.log"
|
|
canary_rc=$?
|
|
set -e
|
|
|
|
set +e
|
|
docker rm -f "$container" >/dev/null 2>&1
|
|
outer_container_remove_rc=$?
|
|
rm -rf "$workdir"
|
|
outer_workdir_remove_rc=$?
|
|
container_name_readback="$(
|
|
docker container ls -a --filter "name=^/${container}$" --format '{{.Names}}' 2>&1
|
|
)"
|
|
container_name_readback_rc=$?
|
|
container_label_readback="$(
|
|
docker container ls -a \
|
|
--filter "label=livingip.canary=proposal-apply-lifecycle" \
|
|
--filter "label=livingip.canary.instance=${container}" \
|
|
--format '{{.Names}}' 2>&1
|
|
)"
|
|
container_label_readback_rc=$?
|
|
if [[ "$container_label_readback_rc" -eq 0 ]]; then
|
|
container_label_orphan_count="$(
|
|
awk 'NF { count += 1 } END { print count + 0 }' <<<"$container_label_readback"
|
|
)"
|
|
else
|
|
container_label_orphan_count=-1
|
|
fi
|
|
if [[ -e "$workdir" || -L "$workdir" ]]; then
|
|
outer_workdir_lexists=true
|
|
else
|
|
outer_workdir_lexists=false
|
|
fi
|
|
set -e
|
|
trap - EXIT
|
|
|
|
source_counts_after="$(source_counts)"
|
|
service_after="$(service_state)"
|
|
|
|
SOURCE_COUNTS_BEFORE="$source_counts_before" \
|
|
SOURCE_COUNTS_AFTER="$source_counts_after" \
|
|
SOURCE_CONTAINER="$source_container" \
|
|
SOURCE_DB="$source_db" \
|
|
SERVICE_BEFORE="$service_before" \
|
|
SERVICE_AFTER="$service_after" \
|
|
REPO_ROOT="$repo_root" \
|
|
OUTER_CONTAINER_REMOVE_RC="$outer_container_remove_rc" \
|
|
OUTER_WORKDIR_REMOVE_RC="$outer_workdir_remove_rc" \
|
|
CONTAINER_NAME_READBACK="$container_name_readback" \
|
|
CONTAINER_NAME_READBACK_RC="$container_name_readback_rc" \
|
|
CONTAINER_LABEL_READBACK="$container_label_readback" \
|
|
CONTAINER_LABEL_READBACK_RC="$container_label_readback_rc" \
|
|
CONTAINER_LABEL_ORPHAN_COUNT="$container_label_orphan_count" \
|
|
OUTER_WORKDIR_LEXISTS="$outer_workdir_lexists" \
|
|
CANARY_RC="$canary_rc" \
|
|
SOURCE_TIER="$source_tier" \
|
|
SOURCE_NETWORK_MODE="$source_network_mode" \
|
|
SOURCE_CANARY_LABEL="$source_canary_label" \
|
|
CANARY_NETWORK_MODE="$container_network_mode" \
|
|
CANARY_MOUNTS_JSON="$container_mounts_json" \
|
|
CANARY_LABEL="$container_canary_label" \
|
|
CANARY_INSTANCE_LABEL="$container_instance_label" \
|
|
python3 "$repo_root/scripts/finalize_approve_claim_isolated_canary.py" "$output"
|