diff --git a/.superpowers/sdd/pgvector-task-4-report.md b/.superpowers/sdd/pgvector-task-4-report.md index 1da90815..38fe1607 100644 --- a/.superpowers/sdd/pgvector-task-4-report.md +++ b/.superpowers/sdd/pgvector-task-4-report.md @@ -83,3 +83,12 @@ All five final review findings were addressed in a follow-up commit: sequence authority, and 768-dimensional compatibility are therefore verified through the production adapter. Reconciliation now restores group-role schema `USAGE`, which table-selected archives cannot carry. + +### Atomic no-replace backup publication + +The final publication review is also closed. The private same-directory archive is published with +an atomic hard-link create rather than rename-overwrite semantics. If any process creates the final +file or symlink after preflight but before publication, `ln` fails with `EEXIST`, the backup exits +nonzero, the concurrent destination remains byte-for-byte intact, and the trap removes only the +randomly named temporary archive owned by this invocation. The fake `pg_dump` safety test creates +that destination immediately before returning and pins the failure and cleanup behavior. diff --git a/scripts/test-vector-backup-restore-safety.sh b/scripts/test-vector-backup-restore-safety.sh index c553244e..1fda9895 100755 --- a/scripts/test-vector-backup-restore-safety.sh +++ b/scripts/test-vector-backup-restore-safety.sh @@ -13,6 +13,9 @@ cat >"$fakebin/pg_dump" <<'SH' set -eu for arg in "$@"; do case "$arg" in --file=*) output=${arg#--file=} ;; esac; done printf 'custom dump' >"$output" +if [ -n "${RACE_OUTPUT:-}" ]; then + printf 'concurrent owner' >"$RACE_OUTPUT" +fi SH chmod 0755 "$fakebin/pg_dump" @@ -26,6 +29,19 @@ test "$(cat "$victim")" = sentinel test "$(cat "$output")" = 'custom dump' test -L "$output.partial" +race_output="$tmp/raced.dump" +if PATH="$fakebin:$PATH" RACE_OUTPUT="$race_output" ./scripts/vector-backup.sh \ + --host source --database thoth --user admin --password-file "$tmp/password" \ + --output "$race_output" >"$tmp/race.out" 2>"$tmp/race.err"; then + echo "backup replaced a destination created concurrently" >&2 + exit 1 +fi +test "$(cat "$race_output")" = 'concurrent owner' +if find "$tmp" -name '.raced.dump.tmp.*' -print | grep -q .; then + echo "backup left its owned temporary archive after publication failure" >&2 + exit 1 +fi + cat >"$fakebin/psql" <<'SH' #!/bin/sh set -eu diff --git a/scripts/vector-backup.sh b/scripts/vector-backup.sh index 030880fd..61b6c586 100755 --- a/scripts/vector-backup.sh +++ b/scripts/vector-backup.sh @@ -45,5 +45,9 @@ PGPASSFILE=$passfile pg_dump \ --format=custom --compress=9 \ --table=vectors.schema_records --table=vectors.evidence --table=vectors.memory \ --table=public.tht_vector_migrations --file="$temporary_output" -mv "$temporary_output" "$output" +if ! ln "$temporary_output" "$output"; then + echo "refusing to replace backup destination created concurrently: $output" >&2 + exit 2 +fi +rm -f "$temporary_output" echo "Vector backup written: $output"