diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5495ec4494..698c9c82c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,15 @@ jobs: - name: Prove complete regression partition run: bin/fm-test-run.sh --check-coverage + sovereign-ledger-mutation-evidence: + name: Sovereign ledger mutation evidence + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + - name: Regenerate and byte-compare maintained evidence + run: tests/fm-sovereign-ledger-redundancy.mutation.sh --verify-evidence + # Two duration-balanced portable parallel shards of the Phase 2 proven-isolated # set only. Composition owner: bin/fm-test-run.sh (docs/fm-test-portable-shards.md). tests-portable-parallel-1: diff --git a/bin/fm-sovereign-ledger-redundancy.sh b/bin/fm-sovereign-ledger-redundancy.sh new file mode 100755 index 0000000000..97a1e43c3e --- /dev/null +++ b/bin/fm-sovereign-ledger-redundancy.sh @@ -0,0 +1,414 @@ +#!/usr/bin/env bash +# fm-sovereign-ledger-redundancy.sh - make, advance, and verify a sovereign-ledger replica. +# +# Usage: +# fm-sovereign-ledger-redundancy.sh [--allow-same-volume-without-device-redundancy] snapshot +# fm-sovereign-ledger-redundancy.sh [--allow-same-volume-without-device-redundancy] refresh +# fm-sovereign-ledger-redundancy.sh [--allow-same-volume-without-device-redundancy] verify +# +# The default admission predicate requires different st_dev values, so a certified replica is on a different device. +# The named opt-out preserves same-volume operation while explicitly giving up device-level redundancy. +# Every public command enters through admit_pair before command-specific code can read, compare, execute, or write the pair. +# This script never admits, rewrites, repairs, or attributes a ruling. +set -euo pipefail + +BUNDLE_MEMBER_COUNT=4 +BUNDLE_ENTRIES= +ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY=no +ADMITTED_PRIMARY= +ADMITTED_REPLICA= +ADMITTED_PRIMARY_DEVICE= +ADMITTED_REPLICA_DEVICE= +ADMITTED_PRIMARY_DIRECTORY_IDENTITY= +ADMITTED_REPLICA_DIRECTORY_IDENTITY= +ADMITTED_PRIMARY_MEMBER_IDENTITIES= +ADMITTED_REPLICA_MEMBER_IDENTITIES= +CREATED_REPLICA= +POSSIBLE_REPLICA= + +die() { + if [ -n "$CREATED_REPLICA" ]; then + printf 'REFUSED: %s; partial replica retained without deletion: %s\n' "$*" "$CREATED_REPLICA" >&2 + else + printf 'REFUSED: %s\n' "$*" >&2 + fi + exit 1 +} + +report_snapshot_signal() { + local signal=$1 status=$2 + trap - HUP INT TERM + printf 'REFUSED: snapshot interrupted by %s; partial replica retained or may exist without deletion: %s\n' "$signal" "$POSSIBLE_REPLICA" >&2 + exit "$status" +} + +usage() { + sed -n '2,12{s/^# \{0,1\}//;p;}' "$0" >&2 + exit 2 +} + +canonical_dir() { + [ -d "$1" ] || die "ledger directory does not exist: $1" + ( + cd -- "$1" + pwd -P + ) +} + +bundle_manifest() { + printf '%s\n' CONTRACT.md fm-sovereign-ledger.sh ledger.tsv tests.sh +} + +collect_bundle_entries() { + local dir=$1 entries required count + # shellcheck disable=SC2016 + if ! entries=$(find "$dir" -mindepth 1 -maxdepth 1 -exec /bin/sh -c ' + for path do + printf "%s\n" "${path##*/}" + done + ' sh {} + | LC_ALL=C sort); then + die "could not enumerate ledger bundle: $dir" + fi + required=$(bundle_manifest) + count=$(printf '%s\n' "$entries" | wc -l | tr -d '[:space:]') + [ "$entries" = "$required" ] \ + || die "ledger bundle manifest differs from the exact required names: $dir" + [ "$count" -eq "$BUNDLE_MEMBER_COUNT" ] \ + || die "ledger bundle must contain exactly $BUNDLE_MEMBER_COUNT manifest members, found $count: $dir" + BUNDLE_ENTRIES=$entries +} + +require_bundle() { + local dir=$1 entry checked=0 + collect_bundle_entries "$dir" + while IFS= read -r entry; do + [ -L "$dir/$entry" ] && die "ledger bundle contains a symlink: $dir/$entry" + [ -f "$dir/$entry" ] || die "ledger bundle contains a non-regular file: $dir/$entry" + checked=$((checked + 1)) + done <<< "$BUNDLE_ENTRIES" + [ "$checked" -eq "$BUNDLE_MEMBER_COUNT" ] \ + || die "ledger bundle manifest check read $checked of $BUNDLE_MEMBER_COUNT members: $dir" + [ -x "$dir/fm-sovereign-ledger.sh" ] || die "ledger verifier is not executable: $dir/fm-sovereign-ledger.sh" +} + +compare_files() { + local primary=$1 replica=$2 skip=${3:-} entry compared=0 expected=$BUNDLE_MEMBER_COUNT + collect_bundle_entries "$primary" + [ -z "$skip" ] || expected=$((expected - 1)) + while IFS= read -r entry; do + [ "$entry" = "$skip" ] && continue + cmp -s "$primary/$entry" "$replica/$entry" \ + || die "replica differs from primary: $entry" + compared=$((compared + 1)) + done <<< "$BUNDLE_ENTRIES" + [ "$compared" -eq "$expected" ] \ + || die "byte comparison read $compared of $expected required bundle members" +} + +portable_device_identity() { + local path=$1 identity + if identity=$(stat -f '%d' "$path" 2>/dev/null); then + : + elif identity=$(stat -c '%d' "$path" 2>/dev/null); then + : + else + return 1 + fi + printf '%s\n' "$identity" | LC_ALL=C grep -Eq '^[0-9]+$' || return 1 + printf '%s\n' "$identity" +} + +portable_directory_identity() { + local path=$1 identity + if identity=$(stat -L -f '%d:%i' "$path" 2>/dev/null); then + : + elif identity=$(stat -L -c '%d:%i' "$path" 2>/dev/null); then + : + else + return 1 + fi + printf '%s\n' "$identity" | LC_ALL=C grep -Eq '^[0-9]+:[0-9]+$' || return 1 + printf '%s\n' "$identity" +} + +portable_member_identity() { + portable_directory_identity "$1" +} + +require_noncontained_pair() { + local primary=$1 replica=$2 + [ "$primary" != "$replica" ] || die "primary and replica directories must differ" + case "$primary/" in "$replica/"*) die "primary and replica directories must not contain one another" ;; esac + case "$replica/" in "$primary/"*) die "primary and replica directories must not contain one another" ;; esac +} + +admit_device_pair() { + local primary=$1 replica=$2 primary_device replica_device + primary_device=$(portable_device_identity "$primary") \ + || die "could not establish primary st_dev identity: $primary" + replica_device=$(portable_device_identity "$replica") \ + || die "could not establish replica st_dev identity: $replica" + if [ "$primary_device" = "$replica_device" ] \ + && [ "$ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY" != yes ]; then + die "primary and replica must be on different devices (st_dev differs); use --allow-same-volume-without-device-redundancy only when explicitly waiving device redundancy" + fi + ADMITTED_PRIMARY_DEVICE=$primary_device + ADMITTED_REPLICA_DEVICE=$replica_device +} + +verify_with_primary() { + local primary=$1 subject=$2 + LEDGER_DIR="$subject" "$primary/fm-sovereign-ledger.sh" verify >/dev/null \ + || die "primary ledger verifier rejected $subject/ledger.tsv" +} + +replica_is_prefix() { + local primary=$1 replica=$2 primary_lines replica_lines + primary_lines=$(wc -l < "$primary/ledger.tsv" | tr -d ' ') + replica_lines=$(wc -l < "$replica/ledger.tsv" | tr -d ' ') + [ "$replica_lines" -gt 0 ] || return 1 + [ "$primary_lines" -gt "$replica_lines" ] || return 1 + head -n "$replica_lines" "$primary/ledger.tsv" | cmp -s - "$replica/ledger.tsv" +} + +require_admitted_directory() { + local label=$1 path=$2 expected=$3 canonical identity + canonical=$(canonical_dir "$path") + [ "$canonical" = "$path" ] || die "$label directory no longer resolves to its admitted path: $path" + identity=$(portable_directory_identity "$path") \ + || die "could not re-establish $label directory identity: $path" + [ "$identity" = "$expected" ] || die "$label directory changed after admission: $path" +} + +admit_independent_bundle_members() { + local primary=$1 replica=$2 entry primary_entry replica_entry identity + local primary_identities='' replica_identities='' primary_identity replica_identity compared=0 + collect_bundle_entries "$primary" + while IFS= read -r entry; do + identity=$(portable_member_identity "$primary/$entry") \ + || die "could not establish primary bundle member identity: $primary/$entry" + primary_identities="${primary_identities}${entry}\t${identity}\n" + identity=$(portable_member_identity "$replica/$entry") \ + || die "could not establish replica bundle member identity: $replica/$entry" + replica_identities="${replica_identities}${entry}\t${identity}\n" + done <<< "$BUNDLE_ENTRIES" + while IFS=$'\t' read -r primary_entry primary_identity; do + while IFS=$'\t' read -r replica_entry replica_identity; do + [ "$primary_identity" != "$replica_identity" ] \ + || die "replica $replica_entry shares storage with primary $primary_entry (device:inode), not an independent file" + compared=$((compared + 1)) + done < <(printf '%b' "$replica_identities") + done < <(printf '%b' "$primary_identities") + [ "$compared" -eq $((BUNDLE_MEMBER_COUNT * BUNDLE_MEMBER_COUNT)) ] \ + || die "bundle member identity proof compared $compared of $((BUNDLE_MEMBER_COUNT * BUNDLE_MEMBER_COUNT)) required pairs" + ADMITTED_PRIMARY_MEMBER_IDENTITIES=$(printf '%b' "$primary_identities") + ADMITTED_REPLICA_MEMBER_IDENTITIES=$(printf '%b' "$replica_identities") +} + +require_admitted_bundle_members() { + local primary=$1 replica=$2 entry expected identity checked=0 + while IFS=$'\t' read -r entry expected; do + identity=$(portable_member_identity "$primary/$entry") \ + || die "could not re-establish primary bundle member identity: $primary/$entry" + [ "$identity" = "$expected" ] || die "primary bundle member changed after admission: $primary/$entry" + checked=$((checked + 1)) + done <<< "$ADMITTED_PRIMARY_MEMBER_IDENTITIES" + while IFS=$'\t' read -r entry expected; do + identity=$(portable_member_identity "$replica/$entry") \ + || die "could not re-establish replica bundle member identity: $replica/$entry" + [ "$identity" = "$expected" ] || die "replica bundle member changed after admission: $replica/$entry" + checked=$((checked + 1)) + done <<< "$ADMITTED_REPLICA_MEMBER_IDENTITIES" + [ "$checked" -eq $((BUNDLE_MEMBER_COUNT * 2)) ] \ + || die "admitted bundle member check read $checked of $((BUNDLE_MEMBER_COUNT * 2)) required identities" +} + +admit_existing_pair() { + local primary=$1 replica=$2 mode=$3 + require_noncontained_pair "$primary" "$replica" + admit_device_pair "$primary" "$replica" + require_bundle "$primary" + require_bundle "$replica" + admit_independent_bundle_members "$primary" "$replica" + compare_files "$primary" "$replica" ledger.tsv + verify_with_primary "$primary" "$primary" + verify_with_primary "$primary" "$replica" + case "$mode" in + exact) compare_files "$primary" "$replica" ;; + prefix) replica_is_prefix "$primary" "$replica" \ + || die "replica is not a verified byte-exact append-only prefix of primary" ;; + inspect) ;; + *) die "internal admission mode is invalid: $mode" ;; + esac + require_bundle "$primary" + require_bundle "$replica" + admit_device_pair "$primary" "$replica" + admit_independent_bundle_members "$primary" "$replica" + compare_files "$primary" "$replica" ledger.tsv + case "$mode" in + exact) compare_files "$primary" "$replica" ;; + prefix) replica_is_prefix "$primary" "$replica" \ + || die "replica changed after prefix admission" ;; + esac + ADMITTED_PRIMARY_DIRECTORY_IDENTITY=$(portable_directory_identity "$primary") \ + || die "could not establish admitted primary directory identity: $primary" + ADMITTED_REPLICA_DIRECTORY_IDENTITY=$(portable_directory_identity "$replica") \ + || die "could not establish admitted replica directory identity: $replica" +} + +copy_file_exclusively() { + local source=$1 destination=$2 + perl -MFcntl=O_WRONLY,O_CREAT,O_EXCL -e ' + use strict; + use warnings; + my ($source, $destination) = @ARGV; + open my $input, "<", $source or die "$source: $!\n"; + binmode $input; + my @source_stat = stat $input; + local $/; + my $content = <$input>; + close $input or die "$source: $!\n"; + sysopen my $output, $destination, O_WRONLY | O_CREAT | O_EXCL, 0600 + or die "$destination: $!\n"; + binmode $output; + my $offset = 0; + while ($offset < length $content) { + my $written = syswrite $output, $content, length($content) - $offset, $offset; + die "$destination: $!\n" unless defined $written; + $offset += $written; + } + chmod($source_stat[2] & 07777, $output) or die "$destination: $!\n"; + close $output or die "$destination: $!\n"; + ' "$source" "$destination" 2>/dev/null \ + || die "could not exclusively copy bundle member: $destination" +} + +copy_bundle() { + local primary=$1 replica=$2 entry copied=0 replica_identity + replica_identity=$(portable_directory_identity "$replica") \ + || die "could not establish new replica directory identity: $replica" + collect_bundle_entries "$primary" + while IFS= read -r entry; do + require_admitted_directory replica "$replica" "$replica_identity" + copy_file_exclusively "$primary/$entry" "$replica/$entry" + copied=$((copied + 1)) + done <<< "$BUNDLE_ENTRIES" + [ "$copied" -eq "$BUNDLE_MEMBER_COUNT" ] \ + || die "bundle copy read $copied of $BUNDLE_MEMBER_COUNT required members" +} + +admit_snapshot_destination() { + local primary=$1 replica_input=$2 parent base replica parent_identity + parent=$(dirname -- "$replica_input") + [ -d "$parent" ] || die "replica parent directory does not exist: $parent" + [ ! -L "$parent" ] || die "replica parent directory is symlinked: $parent" + parent=$(canonical_dir "$parent") + base=$(basename -- "$replica_input") + case "$base" in ''|.|..) die "unsafe replica destination leaf: $replica_input" ;; esac + replica="$parent/$base" + require_noncontained_pair "$primary" "$replica" + [ ! -e "$replica" ] && [ ! -L "$replica" ] \ + || die "replica destination already exists or is symlinked: $replica" + admit_device_pair "$primary" "$parent" + parent_identity=$(portable_directory_identity "$parent") \ + || die "could not establish replica parent directory identity before creation: $parent" + require_admitted_directory "replica parent" "$parent" "$parent_identity" + POSSIBLE_REPLICA=$replica + trap 'report_snapshot_signal HUP 129' HUP + trap 'report_snapshot_signal INT 130' INT + trap 'report_snapshot_signal TERM 143' TERM + mkdir -- "$replica" || die "could not exclusively create replica destination: $replica" + CREATED_REPLICA=$replica + ADMITTED_REPLICA_DIRECTORY_IDENTITY=$(portable_directory_identity "$replica") \ + || die "could not establish new replica directory identity: $replica" + copy_bundle "$primary" "$replica" + admit_existing_pair "$primary" "$replica" exact + ADMITTED_REPLICA=$replica +} + +admit_pair() { + local primary_input=$1 replica_input=$2 mode=$3 primary replica + primary=$(canonical_dir "$primary_input") + if [ "$mode" = snapshot ] && [ ! -e "$replica_input" ] && [ ! -L "$replica_input" ]; then + require_bundle "$primary" + verify_with_primary "$primary" "$primary" + ADMITTED_PRIMARY=$primary + admit_snapshot_destination "$primary" "$replica_input" + return + fi + replica=$(canonical_dir "$replica_input") + require_noncontained_pair "$primary" "$replica" + require_bundle "$primary" + verify_with_primary "$primary" "$primary" + case "$mode" in + snapshot) mode=exact ;; + esac + admit_existing_pair "$primary" "$replica" "$mode" + ADMITTED_PRIMARY=$primary + ADMITTED_REPLICA=$replica +} + +copy_ledger_atomically() { + local primary=$1 replica=$2 parent base tmp + require_admitted_directory primary "$primary" "$ADMITTED_PRIMARY_DIRECTORY_IDENTITY" + require_admitted_directory replica "$replica" "$ADMITTED_REPLICA_DIRECTORY_IDENTITY" + require_admitted_bundle_members "$primary" "$replica" + parent=$(dirname -- "$replica") + base=$(basename -- "$replica") + tmp=$(mktemp "$parent/.${base}.ledger.tsv.tmp.XXXXXX") \ + || die "could not create refresh staging file beside replica" + if ! cp -p "$primary/ledger.tsv" "$tmp"; then + rm -f -- "$tmp" + die "could not stage primary ledger.tsv for refresh" + fi + if ! perl -e 'rename $ARGV[0], $ARGV[1] or exit 1' "$tmp" "$replica/ledger.tsv"; then + rm -f -- "$tmp" + die "could not publish refreshed replica ledger.tsv" + fi +} + +cmd_snapshot() { + admit_pair "$1" "$2" snapshot + if [ -n "$CREATED_REPLICA" ]; then + CREATED_REPLICA= + POSSIBLE_REPLICA= + trap - HUP INT TERM + printf 'SNAPSHOT PASS (replica created: %s)\n' "$ADMITTED_REPLICA" + else + printf 'SNAPSHOT PASS (replica already identical: %s)\n' "$ADMITTED_REPLICA" + fi +} + +cmd_refresh() { + admit_pair "$1" "$2" prefix + copy_ledger_atomically "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA" + admit_pair "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA" exact + printf 'REFRESH PASS (replica advanced to primary)\n' +} + +cmd_verify() { + admit_pair "$1" "$2" inspect + if ! cmp -s "$ADMITTED_PRIMARY/ledger.tsv" "$ADMITTED_REPLICA/ledger.tsv"; then + replica_is_prefix "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA" \ + && die "replica is a verified stale prefix; run refresh to advance it" + die "replica ledger.tsv diverges from primary" + fi + admit_pair "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA" exact + if [ "$ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY" = yes ]; then + printf 'REDUNDANCY VERIFY PASS (exact 4-member manifest byte-read; same-volume device redundancy explicitly waived; st_dev %s=%s)\n' "$ADMITTED_PRIMARY_DEVICE" "$ADMITTED_REPLICA_DEVICE" + else + printf 'REDUNDANCY VERIFY PASS (exact 4-member manifest byte-read; primary and replica st_dev identities disjoint: %s!=%s)\n' "$ADMITTED_PRIMARY_DEVICE" "$ADMITTED_REPLICA_DEVICE" + fi +} + +if [ "${1:-}" = --allow-same-volume-without-device-redundancy ]; then + ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY=yes + shift +fi + +case "${1:-}" in + snapshot) shift; [ "$#" -eq 2 ] || usage; cmd_snapshot "$@" ;; + refresh) shift; [ "$#" -eq 2 ] || usage; cmd_refresh "$@" ;; + verify) shift; [ "$#" -eq 2 ] || usage; cmd_verify "$@" ;; + *) usage ;; +esac diff --git a/docs/documentation-audiences.json b/docs/documentation-audiences.json index 64dea78dc6..1dd61d410a 100644 --- a/docs/documentation-audiences.json +++ b/docs/documentation-audiences.json @@ -108,6 +108,10 @@ } ], "surfaces": [ + { + "path": "tests/fixtures/sovereign-ledger-redundancy/CONTRACT.md", + "audience": "maintainer-verification" + }, { "path": ".agents/skills/afk/SKILL.md", "audience": "agent-runtime" @@ -352,6 +356,10 @@ "path": "docs/verification/runtime-backends.md", "audience": "maintainer-verification" }, + { + "path": "docs/verification/sovereign-ledger-redundancy-mutation.md", + "audience": "maintainer-verification" + }, { "path": "docs/verification/stow-memory.md", "audience": "maintainer-verification" diff --git a/docs/scripts.md b/docs/scripts.md index 484911c380..7741b33127 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -33,6 +33,7 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-herdr-ci-cleanup.sh` | Snapshot and tear down only job-owned `fm-lab-*` sessions in the Herdr CI lane | | `fm-test-run.sh` | Behavior-test runner: selection, portable lanes, proven-isolated `--jobs`, coverage guard, timing/JSON | | `fm-test-isolation-proof.sh` | Concurrent isolation proof and proven-isolated candidate set owner | +| `fm-sovereign-ledger-redundancy.sh` | Create, append-only-refresh, and byte-verify a complete local sovereign-ledger replica bundle | | `fm-ensure-agents-md.sh` | Ensure a project's real `AGENTS.md`, its `CLAUDE.md` symlink, and the canonical self-governance section | | `fm-guard.sh` | Warn on primary-checkout tangles, pending queued wakes, and unhealthy supervision | | `fm-primary-scope-lib.sh` | Shared marker-or-plain-checkout primary-home predicate for tracked hooks | diff --git a/docs/verification/sovereign-ledger-redundancy-mutation.md b/docs/verification/sovereign-ledger-redundancy-mutation.md new file mode 100644 index 0000000000..183d0c975a --- /dev/null +++ b/docs/verification/sovereign-ledger-redundancy-mutation.md @@ -0,0 +1,266 @@ +# Sovereign ledger redundancy mutation evidence + +Audience: maintainer verification. + +Verified on 2026-08-15 against the R5 sovereign-ledger redundancy chokepoint. +The owned denominator spans exact real-name enumeration, the st_dev predicate, the four-by-four member-identity cross-product, both member admission calls, carried-identity refresh recheck, exclusive publication, verification, and all three dispatch entries. +Every run copied the implementation under one scoped `mktemp -d`; the live ledger, `data/`, and `state/` were never inputs or targets. + +```sh +tests/fm-sovereign-ledger-redundancy.mutation.sh --write-evidence sovereign-ledger-redundancy-mutation.candidate.md +``` + +Evidence publication rejects existing destinations, so the candidate is reviewed against this record before replacement rather than overwriting it in place. + +Observed summary: `killed=36 survived=17 void=0 harness_broken=0 denominator=53`; the intentional no-op control recorded `substitutions=1 status=0 outcome=SURVIVED`. + +| Mutant | Stable exact clause anchor | Substitutions | Outcome | Status | Unenforced claim when survived | +| --- | --- | ---: | --- | ---: | --- | +| `M001` | `strict.errexit` | 1 | SURVIVED | 0 | Shell failures cannot fall through. | +| `M002` | `manifest.denominator` | 1 | KILLED | 1 | - | +| `M003` | `canonical.exists` | 1 | KILLED | 1 | - | +| `M004` | `canonical.physical` | 1 | KILLED | 1 | - | +| `M005` | `manifest.tests-member` | 1 | KILLED | 1 | - | +| `M006` | `enumerate.top-level` | 1 | KILLED | 1 | - | +| `M007` | `enumerate.real-names` | 1 | KILLED | 1 | - | +| `M008` | `enumerate.exact-set` | 1 | KILLED | 1 | - | +| `M009` | `require.no-symlink` | 1 | KILLED | 1 | - | +| `M010` | `require.regular` | 1 | KILLED | 1 | - | +| `M011` | `require.executable` | 1 | KILLED | 1 | - | +| `M012` | `bytes.reject` | 1 | KILLED | 1 | - | +| `M013` | `device.bsd-read` | 1 | KILLED | 1 | - | +| `M014` | `device.gnu-read` | 1 | KILLED | 1 | - | +| `M015` | `device.numeric-shape` | 1 | KILLED | 1 | - | +| `M016` | `containment.distinct` | 1 | KILLED | 1 | - | +| `M017` | `containment.primary` | 1 | KILLED | 1 | - | +| `M018` | `containment.replica` | 1 | KILLED | 1 | - | +| `M019` | `device.primary-read` | 1 | KILLED | 1 | - | +| `M020` | `device.replica-read` | 1 | KILLED | 1 | - | +| `M021` | `device.inequality` | 1 | KILLED | 1 | - | +| `M022` | `device.optout-only` | 1 | KILLED | 1 | - | +| `M023` | `verifier.execute` | 1 | KILLED | 1 | - | +| `M024` | `prefix.nonempty` | 1 | KILLED | 1 | - | +| `M025` | `prefix.shorter` | 1 | KILLED | 1 | - | +| `M026` | `prefix.leading` | 1 | KILLED | 1 | - | +| `M027` | `recheck.replica-type` | 1 | SURVIVED | 0 | Admission reclassifies replica entries after verifier execution. | +| `M028` | `directory.canonical-stable` | 1 | SURVIVED | 0 | A directory path cannot change after admission. | +| `M029` | `directory.identity-stable` | 1 | SURVIVED | 0 | A directory object cannot change after admission. | +| `M030` | `publish.exclusive-create` | 1 | SURVIVED | 0 | Bundle members are created exclusively. | +| `M031` | `publish.preserve-mode` | 1 | KILLED | 1 | - | +| `M032` | `publish.parent-no-symlink` | 1 | SURVIVED | 0 | Snapshot parent cannot be a symlink. | +| `M033` | `publish.safe-leaf` | 1 | SURVIVED | 0 | Snapshot leaf is safe. | +| `M034` | `publish.destination-absent` | 1 | SURVIVED | 0 | Snapshot destination is absent and not symlinked. | +| `M035` | `publish.parent-device` | 1 | SURVIVED | 0 | Snapshot checks destination-volume st_dev before creation. | +| `M036` | `publish.mkdir-exclusive` | 1 | SURVIVED | 0 | Snapshot claims its final destination exclusively. | +| `M037` | `publish.copy-bundle` | 1 | KILLED | 1 | - | +| `M038` | `publish.validate-object` | 1 | SURVIVED | 0 | Snapshot validates the object it published. | +| `M039` | `refresh.prefix-admission` | 1 | KILLED | 1 | - | +| `M040` | `refresh.atomic-copy` | 1 | KILLED | 1 | - | +| `M041` | `refresh.post-admission` | 1 | SURVIVED | 0 | Refresh re-admits the published exact pair. | +| `M042` | `verify.inspect-admission` | 1 | KILLED | 1 | - | +| `M043` | `verify.exact-recheck` | 1 | SURVIVED | 0 | Verify re-admits an exact pair before PASS. | +| `M044` | `option.named-flag` | 1 | KILLED | 1 | - | +| `M045` | `dispatch.snapshot` | 1 | KILLED | 1 | - | +| `M046` | `dispatch.refresh` | 1 | KILLED | 1 | - | +| `M047` | `dispatch.verify` | 1 | KILLED | 1 | - | +| `M048` | `identity.member-read` | 1 | KILLED | 1 | - | +| `M049` | `identity.cross-product-disjoint` | 1 | KILLED | 1 | - | +| `M050` | `identity.cross-product-denominator` | 1 | SURVIVED | 0 | The complete four-by-four member cross-product is proved. | +| `M051` | `admission.initial-member-identity` | 1 | SURVIVED | 0 | Initial pair admission proves member independence before verifier execution. | +| `M052` | `admission.final-member-identity` | 1 | SURVIVED | 0 | Final pair admission re-proves member independence after verifier execution. | +| `M053` | `refresh.carried-member-identity` | 1 | SURVIVED | 0 | Refresh rechecks the admitted member identities before publication. | + +## Evidence publication containment + +The complete fixture results, cell outcomes, alternate defenders, per-mutant totals, and aggregate denominator below come from one canonical generated stream. + +# Evidence publication containment matrix + +Generated by `tests/fm-sovereign-ledger-evidence-publish.mutation.sh --markdown`. + +```text + PASS absolute destination aimed at fake data is refused + PASS dot-dot traversal aimed at fake data is refused + PASS symlinked leaf aimed at fake data is refused + PASS symlinked parent directory is refused + PASS symlinked parent aimed at fake state is refused + PASS destination resolving outside after parent resolution is refused + PASS existing hard-linked destination is refused without mutation + PASS unresolved destination parent is refused + PASS existing destination is refused without mutation + PASS symlinked evidence scope is refused + PASS unresolved evidence scope is refused + PASS unsafe destination leaf is refused + PASS legitimate in-scope publication remains exact +CONTAINMENT FIXTURES passed=13 failed=0 +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=absolute outcome=KILLED defender=- +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=traversal outcome=KILLED defender=- +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=symlink-parent outcome=KILLED defender=- +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=symlink-parent-state outcome=KILLED defender=- +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=resolved-outside outcome=KILLED defender=- +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=hard-link outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=natural-unresolved-parent +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=existing outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=scope-symlink outcome=KILLED defender=- +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=natural-unresolved-parent +PUBLISH CELL mutant=P001 anchor=resolved-path-validator-call substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH MUTANT P001 anchor=resolved-path-validator-call substitutions=1 killed=6 survived=6 void=0 +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=absolute outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=traversal outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=hard-link outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=existing outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=resolved-path-validator +PUBLISH CELL mutant=P002 anchor=exclusive-create-flag substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=resolved-path-validator +PUBLISH MUTANT P002 anchor=exclusive-create-flag substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=symlink-parent outcome=KILLED defender=- +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P003 anchor=symlink-component-precheck substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P003 anchor=symlink-component-precheck substitutions=1 killed=1 survived=11 void=0 +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=final-structural-containment +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P004 anchor=canonical-structural-containment substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P004 anchor=canonical-structural-containment substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P005 anchor=absolute-path-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P005 anchor=absolute-path-guard substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P006 anchor=unsafe-component-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P006 anchor=unsafe-component-guard substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P007 anchor=symlink-leaf-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P007 anchor=symlink-leaf-guard substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=existing outcome=SURVIVED defender=exclusive-create-O_EXCL +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P008 anchor=existing-leaf-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P008 anchor=existing-leaf-guard substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=natural-unresolved-parent +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P009 anchor=canonical-parent-resolution substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P009 anchor=canonical-parent-resolution substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P010 anchor=final-structural-containment substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P010 anchor=final-structural-containment substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P011 anchor=scope-symlink-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P011 anchor=scope-symlink-guard substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P012 anchor=canonical-scope-resolution substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=unsafe-leaf-guard +PUBLISH MUTANT P012 anchor=canonical-scope-resolution substitutions=1 killed=0 survived=12 void=0 +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=absolute outcome=SURVIVED defender=absolute-path-guard +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=traversal outcome=SURVIVED defender=unsafe-component-guard +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=symlink-leaf-data outcome=SURVIVED defender=symlink-leaf-guard +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=symlink-parent outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=symlink-parent-state outcome=SURVIVED defender=symlink-component-precheck +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=resolved-outside outcome=SURVIVED defender=canonical-structural-containment +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=hard-link outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=unresolved-parent outcome=SURVIVED defender=canonical-parent-resolution +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=existing outcome=SURVIVED defender=existing-leaf-guard +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=scope-symlink outcome=SURVIVED defender=scope-symlink-guard +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=scope-unresolved outcome=SURVIVED defender=canonical-scope-resolution +PUBLISH CELL mutant=P013 anchor=unsafe-leaf-guard substitutions=1 fixture=unsafe-leaf outcome=SURVIVED defender=existing-leaf-guard +PUBLISH MUTANT P013 anchor=unsafe-leaf-guard substitutions=1 killed=0 survived=12 void=0 +PUBLISH MATRIX SUMMARY mechanisms=14 written_boundaries=13 natural_boundaries=1 mutants=13 fixtures=12 cells=156 killed=7 survived=149 void=0 +``` diff --git a/tests/fixtures/sovereign-ledger-redundancy/CONTRACT.md b/tests/fixtures/sovereign-ledger-redundancy/CONTRACT.md new file mode 100644 index 0000000000..fbc3bdd756 --- /dev/null +++ b/tests/fixtures/sovereign-ledger-redundancy/CONTRACT.md @@ -0,0 +1,3 @@ +# Fixture sovereign ledger contract + +This fixture models the public `verify`, `recheck`, and `text` commands consumed by the redundancy mechanism tests. diff --git a/tests/fixtures/sovereign-ledger-redundancy/fm-sovereign-ledger.sh b/tests/fixtures/sovereign-ledger-redundancy/fm-sovereign-ledger.sh new file mode 100755 index 0000000000..0d2c555dd5 --- /dev/null +++ b/tests/fixtures/sovereign-ledger-redundancy/fm-sovereign-ledger.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Fixture ledger verifier for the redundancy mechanism's public dependency boundary. +set -euo pipefail + +ledger_dir=${LEDGER_DIR:?LEDGER_DIR is required} +ledger=$ledger_dir/ledger.tsv + +verify() { + [ -f "$ledger" ] || exit 1 + awk -F '\t' ' + NF != 3 { bad = 1 } + $1 !~ /^ruling-[1-5]$/ { bad = 1 } + seen[$1]++ != 0 { bad = 1 } + END { exit bad || length(seen) < 4 ? 1 : 0 } + ' "$ledger" +} + +recheck() { + local key source count=0 bad=0 + while IFS=$'\t' read -r key source _; do + count=$((count + 1)) + if [ ! -f "$source" ]; then + printf 'SOURCE_GONE %s (%s)\n' "$key" "$source" + bad=$((bad + 1)) + fi + done < "$ledger" + printf 'recheck: %s entries, %s divergent\n' "$count" "$bad" + [ "$bad" -eq 0 ] +} + +text() { + awk -F '\t' -v key="$1" '$1 == key { print $3; found = 1 } END { exit found ? 0 : 1 }' "$ledger" | base64 -d +} + +case "${1:-}" in + verify) verify ;; + recheck) recheck ;; + text) text "${2:?ruling key}" ;; + *) exit 2 ;; +esac diff --git a/tests/fixtures/sovereign-ledger-redundancy/tests.sh b/tests/fixtures/sovereign-ledger-redundancy/tests.sh new file mode 100755 index 0000000000..5c2074dbbe --- /dev/null +++ b/tests/fixtures/sovereign-ledger-redundancy/tests.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# Fixture companion test file: copied and compared as part of the complete bundle. +exit 0 diff --git a/tests/fm-sovereign-ledger-cross-volume.test.sh b/tests/fm-sovereign-ledger-cross-volume.test.sh new file mode 100755 index 0000000000..bc50fc6792 --- /dev/null +++ b/tests/fm-sovereign-ledger-cross-volume.test.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# Prove the strict default accepts a genuine replica on a separately mounted volume. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TOOL="$ROOT/bin/fm-sovereign-ledger-redundancy.sh" +FIXTURE="$ROOT/tests/fixtures/sovereign-ledger-redundancy" +TMP="$(mktemp -d)" +VOLUME_ROOT= +ATTACHED_VOLUME=no + +cleanup_fixture() { + local scratch=$1 volume_root=$2 attached=$3 hdiutil_command=$4 + if [ "$attached" = yes ]; then + if ! "$hdiutil_command" detach "$volume_root" >/dev/null 2>&1; then + printf 'FAIL could not detach fixture volume; retained scratch and image at %s\n' "$scratch" >&2 + return 1 + fi + attached=no + fi + rm -rf -- "$scratch" + if [ -n "$volume_root" ] && [ "$attached" = no ] && [[ "$volume_root" != "$scratch"/* ]]; then + rm -rf -- "$volume_root" + fi +} + +finish() { + local status=$? + if ! cleanup_fixture "$TMP" "$VOLUME_ROOT" "$ATTACHED_VOLUME" hdiutil; then + status=1 + fi + trap - EXIT HUP INT TERM + exit "$status" +} +trap finish EXIT +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM + +FAKE_HDIUTIL="$TMP/fake-hdiutil" +printf '%s\n' \ + '#!/usr/bin/env bash' \ + "printf '%s\n' \"\$*\" >> \"\$CLEANUP_HDIUTIL_LOG\"" \ + "exit \"\$CLEANUP_HDIUTIL_STATUS\"" > "$FAKE_HDIUTIL" +chmod +x "$FAKE_HDIUTIL" +export CLEANUP_HDIUTIL_LOG CLEANUP_HDIUTIL_STATUS + +CLEANUP_FAILURE="$TMP/cleanup-failure" +CLEANUP_FAILURE_VOLUME="$CLEANUP_FAILURE/volume" +CLEANUP_FAILURE_LOG="$TMP/cleanup-failure.log" +CLEANUP_FAILURE_DIAGNOSTIC="$TMP/cleanup-failure.diagnostic" +mkdir -p "$CLEANUP_FAILURE_VOLUME/replica" +: > "$CLEANUP_FAILURE/ledger-volume.dmg" +: > "$CLEANUP_FAILURE_VOLUME/replica/ledger.tsv" +if CLEANUP_HDIUTIL_LOG="$CLEANUP_FAILURE_LOG" CLEANUP_HDIUTIL_STATUS=1 \ + cleanup_fixture "$CLEANUP_FAILURE" "$CLEANUP_FAILURE_VOLUME" yes "$FAKE_HDIUTIL" 2> "$CLEANUP_FAILURE_DIAGNOSTIC"; then + printf 'FAIL cleanup accepted a failed fixture-volume detach\n' >&2 + exit 1 +fi +[ "$(cat "$CLEANUP_FAILURE_LOG")" = "detach $CLEANUP_FAILURE_VOLUME" ] \ + && grep -Fq 'retained scratch and image' "$CLEANUP_FAILURE_DIAGNOSTIC" \ + && [ -f "$CLEANUP_FAILURE/ledger-volume.dmg" ] \ + && [ -f "$CLEANUP_FAILURE_VOLUME/replica/ledger.tsv" ] || { + printf 'FAIL failed detach did not retain the fixture scratch, image, and mounted-path contents\n' >&2 + exit 1 +} +rm -rf -- "$CLEANUP_FAILURE" + +CLEANUP_SUCCESS="$TMP/cleanup-success" +CLEANUP_SUCCESS_VOLUME="$CLEANUP_SUCCESS/volume" +CLEANUP_SUCCESS_LOG="$TMP/cleanup-success.log" +mkdir -p "$CLEANUP_SUCCESS_VOLUME/replica" +: > "$CLEANUP_SUCCESS/ledger-volume.dmg" +CLEANUP_HDIUTIL_LOG="$CLEANUP_SUCCESS_LOG" CLEANUP_HDIUTIL_STATUS=0 \ + cleanup_fixture "$CLEANUP_SUCCESS" "$CLEANUP_SUCCESS_VOLUME" yes "$FAKE_HDIUTIL" +[ "$(cat "$CLEANUP_SUCCESS_LOG")" = "detach $CLEANUP_SUCCESS_VOLUME" ] \ + && [ ! -e "$CLEANUP_SUCCESS" ] || { + printf 'FAIL successful detach did not remove the fixture scratch and image\n' >&2 + exit 1 +} + +device_identity() { + local identity + if identity=$(stat -f '%d' "$1" 2>/dev/null); then + : + elif identity=$(stat -c '%d' "$1" 2>/dev/null); then + : + else + return 1 + fi + printf '%s\n' "$identity" +} + +if [ -d /dev/shm ] && [ -w /dev/shm ] \ + && [ "$(device_identity /dev/shm)" != "$(device_identity "$TMP")" ]; then + VOLUME_ROOT=$(mktemp -d /dev/shm/fm-ledger-volume.XXXXXX) +elif command -v hdiutil >/dev/null 2>&1; then + VOLUME_ROOT="$TMP/ledger-volume" + mkdir "$VOLUME_ROOT" + hdiutil create -quiet -size 32m -fs APFS -volname fm-ledger-r5 "$TMP/ledger-volume.dmg" + ATTACHED_VOLUME=yes + hdiutil attach -quiet -nobrowse -mountpoint "$VOLUME_ROOT" "$TMP/ledger-volume.dmg" +else + printf 'FAIL no writable separate volume is available for the strict acceptance proof\n' >&2 + exit 1 +fi + +PRIMARY="$TMP/primary" +REPLICA="$VOLUME_ROOT/replica" +mkdir "$PRIMARY" +cp "$FIXTURE/CONTRACT.md" "$PRIMARY/CONTRACT.md" +cp "$FIXTURE/fm-sovereign-ledger.sh" "$PRIMARY/fm-sovereign-ledger.sh" +cp "$FIXTURE/tests.sh" "$PRIMARY/tests.sh" +chmod +x "$PRIMARY/fm-sovereign-ledger.sh" "$PRIMARY/tests.sh" +: > "$PRIMARY/ledger.tsv" +for number in 1 2 3 4; do + printf 'ruling-%s\t/source-%s\tZml4dHVyZQo=\n' "$number" "$number" >> "$PRIMARY/ledger.tsv" +done + +PRIMARY_DEVICE=$(device_identity "$PRIMARY") +REPLICA_PARENT_DEVICE=$(device_identity "$VOLUME_ROOT") +[ "$PRIMARY_DEVICE" != "$REPLICA_PARENT_DEVICE" ] || { + printf 'FAIL separate-volume fixture unexpectedly shares st_dev %s\n' "$PRIMARY_DEVICE" >&2 + exit 1 +} +"$TOOL" snapshot "$PRIMARY" "$REPLICA" >/dev/null +"$TOOL" verify "$PRIMARY" "$REPLICA" >/dev/null +printf '2 passed, 0 failed (strict cross-volume snapshot and verify; st_dev %s!=%s)\n' "$PRIMARY_DEVICE" "$REPLICA_PARENT_DEVICE" diff --git a/tests/fm-sovereign-ledger-evidence-publish.mutation.sh b/tests/fm-sovereign-ledger-evidence-publish.mutation.sh new file mode 100755 index 0000000000..d87c3fd2a8 --- /dev/null +++ b/tests/fm-sovereign-ledger-evidence-publish.mutation.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Verify evidence-publication containment and each independent enforcing boundary. +# Mutation anchors and Markdown code spans intentionally preserve shell expressions as literal data. +# shellcheck disable=SC2016 +set -euo pipefail + +MARKDOWN=0 +if [ "${1:-}" = --markdown ]; then + [ "$#" -eq 1 ] || { printf 'usage: %s [--markdown]\n' "$0" >&2; exit 2; } + MARKDOWN=1 +elif [ "$#" -ne 0 ]; then + printf 'usage: %s [--markdown]\n' "$0" >&2 + exit 2 +fi + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SUBJECT="$ROOT/tests/fm-sovereign-ledger-redundancy.mutation.sh" +TMP="$(mktemp -d)" +trap 'rm -rf -- "$TMP"' EXIT +fixtures='absolute traversal symlink-leaf-data symlink-parent symlink-parent-state resolved-outside hard-link unresolved-parent existing scope-symlink scope-unresolved unsafe-leaf' +killed=0 +survived=0 +void=0 +mutants=0 +cells=0 + +if [ "$MARKDOWN" -eq 1 ]; then + printf '# Evidence publication containment matrix\n\n' + printf 'Generated by `%s --markdown`.\n\n' "${0#"$ROOT/"}" + printf '```text\n' +fi +"$SUBJECT" --self-test-evidence-containment + +surviving_defender() { + local mutant=$1 fixture=$2 + case "$mutant:$fixture" in + P001:symlink-leaf-data|P001:hard-link|P001:existing) + printf '%s\n' exclusive-create-O_EXCL ;; + P001:unresolved-parent) + printf '%s\n' natural-unresolved-parent ;; + P001:scope-unresolved) + printf '%s\n' natural-unresolved-parent ;; + P001:unsafe-leaf) + printf '%s\n' exclusive-create-O_EXCL ;; + P002:*) + printf '%s\n' resolved-path-validator ;; + P003:symlink-parent-state|P003:resolved-outside) + printf '%s\n' canonical-structural-containment ;; + P004:resolved-outside) + printf '%s\n' final-structural-containment ;; + P005:absolute) + printf '%s\n' unsafe-component-guard ;; + P006:traversal) + printf '%s\n' canonical-structural-containment ;; + P007:symlink-leaf-data) + printf '%s\n' exclusive-create-O_EXCL ;; + P008:hard-link|P008:existing) + printf '%s\n' exclusive-create-O_EXCL ;; + P009:unresolved-parent) + printf '%s\n' natural-unresolved-parent ;; + P011:scope-symlink) + printf '%s\n' canonical-scope-resolution ;; + P012:scope-unresolved) + printf '%s\n' canonical-parent-resolution ;; + P013:unsafe-leaf) + printf '%s\n' existing-leaf-guard ;; + *) + case "$fixture" in + absolute) printf '%s\n' absolute-path-guard ;; + traversal) printf '%s\n' unsafe-component-guard ;; + symlink-leaf-data) printf '%s\n' symlink-leaf-guard ;; + symlink-parent|symlink-parent-state) printf '%s\n' symlink-component-precheck ;; + resolved-outside) printf '%s\n' canonical-structural-containment ;; + hard-link|existing) printf '%s\n' existing-leaf-guard ;; + unresolved-parent) printf '%s\n' canonical-parent-resolution ;; + scope-symlink) printf '%s\n' scope-symlink-guard ;; + scope-unresolved) printf '%s\n' canonical-scope-resolution ;; + unsafe-leaf) printf '%s\n' unsafe-leaf-guard ;; + esac ;; + esac +} + +publish_mutant() { + local id=$1 anchor=$2 from=$3 to=$4 mutant count_file substitutions + local fixture outcome defender mutant_killed=0 mutant_survived=0 mutant_void=0 + mutants=$((mutants + 1)) + mutant="$TMP/$id.sh" + count_file="$TMP/$id.substitutions" + cp "$SUBJECT" "$mutant" + chmod +x "$mutant" + FROM_TEXT=$from TO_TEXT=$to COUNT_FILE=$count_file perl -0pi -e ' + BEGIN { + $from = $ENV{FROM_TEXT}; + $to = $ENV{TO_TEXT}; + $count_file = $ENV{COUNT_FILE}; + } + $count = 0; + $offset = 0; + while (($found = index($_, $from, $offset)) >= 0) { + $count++; + $offset = $found + length($from); + } + s/\Q$from\E/$to/ if $count == 1; + open(my $fh, ">", $count_file) or die $!; + print {$fh} "$count\n"; + close($fh) or die $!; + ' "$mutant" + substitutions=$(cat "$count_file") + for fixture in $fixtures; do + cells=$((cells + 1)) + if [ "$substitutions" -ne 1 ]; then + outcome=VOID + defender=- + void=$((void + 1)) + mutant_void=$((mutant_void + 1)) + elif "$mutant" --self-test-evidence-fixture "$fixture" > "$TMP/$id-$fixture.out" 2>&1; then + outcome=SURVIVED + defender=$(surviving_defender "$id" "$fixture") + survived=$((survived + 1)) + mutant_survived=$((mutant_survived + 1)) + else + outcome=KILLED + defender=- + killed=$((killed + 1)) + mutant_killed=$((mutant_killed + 1)) + fi + printf 'PUBLISH CELL mutant=%s anchor=%s substitutions=%s fixture=%s outcome=%s defender=%s\n' \ + "$id" "$anchor" "$substitutions" "$fixture" "$outcome" "$defender" + done + printf 'PUBLISH MUTANT %s anchor=%s substitutions=%s killed=%s survived=%s void=%s\n' \ + "$id" "$anchor" "$substitutions" "$mutant_killed" "$mutant_survived" "$mutant_void" +} + +publish_mutant P001 resolved-path-validator-call \ + 'evidence_validate_destination "$scope" "$relative" "$destination" || return 1' \ + ':' +publish_mutant P002 exclusive-create-flag \ + $'sysopen my $output, $destination, O_WRONLY | O_CREAT | O_EXCL, 0600\n or die "$destination: $!\\n";' \ + $'sysopen my $output, $destination, O_WRONLY | O_CREAT, 0600\n or die "$destination: $!\\n";' +publish_mutant P003 symlink-component-precheck \ + '[ ! -L "$cursor/$component" ]' \ + 'true' +publish_mutant P004 canonical-structural-containment \ + 'case "$resolved/" in "$scope/"*) ;; *) evidence_refuse "evidence destination resolves outside its scope: $relative"; return 1 ;; esac' \ + ':' +publish_mutant P005 absolute-path-guard \ + 'case "$relative" in /*) evidence_refuse "absolute evidence destination is forbidden: $relative"; return 1 ;; esac' \ + ':' +publish_mutant P006 unsafe-component-guard \ + "case \"\$component\" in ''|.|..) evidence_refuse \"unsafe evidence destination component: \$relative\"; return 1 ;; esac" \ + ':' +publish_mutant P007 symlink-leaf-guard \ + '[ ! -L "$cursor/$leaf" ]' \ + 'true' +publish_mutant P008 existing-leaf-guard \ + '[ ! -e "$cursor/$leaf" ]' \ + 'true' +publish_mutant P009 canonical-parent-resolution \ + '|| { evidence_refuse "evidence destination parent is unresolved: $relative"; return 1; }' \ + '|| resolved="$cursor/$component"' +publish_mutant P010 final-structural-containment \ + 'case "$cursor/$leaf" in "$scope/"*) ;; *) evidence_refuse "evidence destination is outside its scope: $relative"; return 1 ;; esac' \ + ':' +publish_mutant P011 scope-symlink-guard \ + '[ ! -L "$scope_input" ]' \ + 'true' +publish_mutant P012 canonical-scope-resolution \ + '|| { evidence_refuse "evidence scope is unresolved: $scope_input"; return 1; }' \ + '|| scope=$scope_input' +publish_mutant P013 unsafe-leaf-guard \ + "case \"\$leaf\" in ''|.|..) evidence_refuse \"unsafe evidence destination leaf: \$relative\"; return 1 ;; esac" \ + ':' +"$SUBJECT" --self-test-evidence-containment >/dev/null +printf 'PUBLISH MATRIX SUMMARY mechanisms=14 written_boundaries=13 natural_boundaries=1 mutants=%s fixtures=12 cells=%s killed=%s survived=%s void=%s\n' \ + "$mutants" "$cells" "$killed" "$survived" "$void" +[ "$mutants" -eq 13 ] +[ "$cells" -eq 156 ] +[ "$killed" -eq 7 ] +[ "$survived" -eq 149 ] +[ "$void" -eq 0 ] +if [ "$MARKDOWN" -eq 1 ]; then + printf '```\n' +fi diff --git a/tests/fm-sovereign-ledger-redundancy.mutation.sh b/tests/fm-sovereign-ledger-redundancy.mutation.sh new file mode 100755 index 0000000000..818cf2d994 --- /dev/null +++ b/tests/fm-sovereign-ledger-redundancy.mutation.sh @@ -0,0 +1,477 @@ +#!/usr/bin/env bash +# Run the owned mutation population for sovereign-ledger redundancy enforcement. +# Mutation anchors and Markdown code spans intentionally preserve shell expressions as literal data. +# shellcheck disable=SC2016 +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SOURCE="$ROOT/bin/fm-sovereign-ledger-redundancy.sh" +TEST="$ROOT/tests/fm-sovereign-ledger-redundancy.test.sh" +EVIDENCE= +EMIT_EVIDENCE=0 +VERIFY_EVIDENCE=0 +REFRESH_EVIDENCE=0 +MODE=mutation +EVIDENCE_FIXTURE= +EVIDENCE_SWAP_PATH= +EVIDENCE_SWAP_TARGET= +if [ "${1:-}" = --emit-evidence ]; then + [ "$#" -eq 1 ] || { printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2; exit 2; } + EMIT_EVIDENCE=1 + exec 3>&1 + exec >&2 +elif [ "${1:-}" = --refresh-evidence ]; then + [ "$#" -eq 1 ] || { printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2; exit 2; } + REFRESH_EVIDENCE=1 +elif [ "${1:-}" = --verify-evidence ]; then + [ "$#" -eq 1 ] || { printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2; exit 2; } + VERIFY_EVIDENCE=1 +elif [ "${1:-}" = --self-test-evidence-fixture ]; then + [ "$#" -eq 2 ] || { printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2; exit 2; } + MODE=fixture + EVIDENCE_FIXTURE=$2 +elif [ "${1:-}" = --self-test-evidence-containment ]; then + [ "$#" -eq 1 ] || { printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2; exit 2; } + MODE=containment +elif [ "${1:-}" = --write-evidence ]; then + [ "$#" -eq 2 ] || { printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2; exit 2; } + EVIDENCE=$2 +elif [ "$#" -ne 0 ]; then + printf 'usage: %s [--emit-evidence|--refresh-evidence|--verify-evidence|--write-evidence |--self-test-evidence-containment|--self-test-evidence-fixture ]\n' "$0" >&2 + exit 2 +fi + +TMP="$(mktemp -d)" +trap 'rm -rf -- "$TMP"' EXIT +RESULTS="$TMP/results.tsv" +killed=0 +survived=0 +void=0 +harness_broken=0 +denominator=0 + +evidence_refuse() { + printf 'REFUSED: %s\n' "$*" >&2 + return 1 +} + +canonical_evidence_dir() { + local path=$1 permit_prechecked_link=${2:-no} + [ -d "$path" ] || return 1 + [ "$permit_prechecked_link" = yes ] || [ ! -L "$path" ] || return 1 + ( + cd -- "$path" 2>/dev/null + pwd -P + ) +} + +evidence_after_component_precheck() { + local candidate=$1 + if [ -n "$EVIDENCE_SWAP_PATH" ] && [ "$candidate" = "$EVIDENCE_SWAP_PATH" ]; then + rmdir -- "$candidate" || return 1 + ln -s "$EVIDENCE_SWAP_TARGET" "$candidate" || return 1 + EVIDENCE_SWAP_PATH= + fi +} + +evidence_validate_destination() { + local scope_input=$1 relative=$2 scope cursor remaining component resolved leaf + case "$relative" in /*) evidence_refuse "absolute evidence destination is forbidden: $relative"; return 1 ;; esac + [ ! -L "$scope_input" ] \ + || { evidence_refuse "evidence scope is symlinked: $scope_input"; return 1; } + scope=$(canonical_evidence_dir "$scope_input") \ + || { evidence_refuse "evidence scope is unresolved: $scope_input"; return 1; } + cursor=$scope + remaining=$relative + while [[ "$remaining" == */* ]]; do + component=${remaining%%/*} + remaining=${remaining#*/} + case "$component" in ''|.|..) evidence_refuse "unsafe evidence destination component: $relative"; return 1 ;; esac + [ ! -L "$cursor/$component" ] \ + || { evidence_refuse "evidence destination has a symlinked parent: $relative"; return 1; } + evidence_after_component_precheck "$cursor/$component" \ + || { evidence_refuse "evidence destination precheck transition failed: $relative"; return 1; } + resolved=$(canonical_evidence_dir "$cursor/$component" yes) \ + || { evidence_refuse "evidence destination parent is unresolved: $relative"; return 1; } + case "$resolved/" in "$scope/"*) ;; *) evidence_refuse "evidence destination resolves outside its scope: $relative"; return 1 ;; esac + cursor=$resolved + done + leaf=$remaining + case "$leaf" in ''|.|..) evidence_refuse "unsafe evidence destination leaf: $relative"; return 1 ;; esac + [ ! -L "$cursor/$leaf" ] \ + || { evidence_refuse "evidence destination leaf is symlinked: $relative"; return 1; } + [ ! -e "$cursor/$leaf" ] \ + || { evidence_refuse "evidence destination already exists: $relative"; return 1; } + case "$cursor/$leaf" in "$scope/"*) ;; *) evidence_refuse "evidence destination is outside its scope: $relative"; return 1 ;; esac + EVIDENCE_DESTINATION="$cursor/$leaf" +} + +publish_evidence() { + local scope=$1 relative=$2 source=$3 destination + [ -f "$source" ] && [ ! -L "$source" ] \ + || { evidence_refuse "evidence source is not a regular file"; return 1; } + case "$relative" in /*) destination=$relative ;; *) destination="$scope/$relative" ;; esac + EVIDENCE_DESTINATION=$destination + evidence_validate_destination "$scope" "$relative" "$destination" || return 1 + destination=$EVIDENCE_DESTINATION + perl -MFcntl=O_WRONLY,O_CREAT,O_EXCL -e ' + use strict; + use warnings; + my ($source, $destination) = @ARGV; + open my $input, "<", $source or die "$source: $!\n"; + binmode $input; + local $/; + my $content = <$input>; + close $input or die "$source: $!\n"; + sysopen my $output, $destination, O_WRONLY | O_CREAT | O_EXCL, 0600 + or die "$destination: $!\n"; + binmode $output; + my $offset = 0; + while ($offset < length $content) { + my $written = syswrite $output, $content, length($content) - $offset, $offset; + die "$destination: $!\n" unless defined $written; + $offset += $written; + } + close $output or die "$destination: $!\n"; + ' "$source" "$destination" 2>/dev/null \ + || { evidence_refuse "could not exclusively publish evidence: $relative"; return 1; } + cmp -s "$source" "$destination" \ + || { evidence_refuse "published evidence did not retain exact bytes: $relative"; return 1; } +} + +evidence_fixture_ids() { + printf '%s\n' absolute traversal symlink-leaf-data symlink-parent symlink-parent-state resolved-outside hard-link unresolved-parent existing scope-symlink scope-unresolved unsafe-leaf legitimate +} + +evidence_fixture_description() { + case "$1" in + absolute) printf '%s\n' 'absolute destination aimed at fake data is refused' ;; + traversal) printf '%s\n' 'dot-dot traversal aimed at fake data is refused' ;; + symlink-leaf-data) printf '%s\n' 'symlinked leaf aimed at fake data is refused' ;; + symlink-parent) printf '%s\n' 'symlinked parent directory is refused' ;; + symlink-parent-state) printf '%s\n' 'symlinked parent aimed at fake state is refused' ;; + resolved-outside) printf '%s\n' 'destination resolving outside after parent resolution is refused' ;; + hard-link) printf '%s\n' 'existing hard-linked destination is refused without mutation' ;; + unresolved-parent) printf '%s\n' 'unresolved destination parent is refused' ;; + existing) printf '%s\n' 'existing destination is refused without mutation' ;; + scope-symlink) printf '%s\n' 'symlinked evidence scope is refused' ;; + scope-unresolved) printf '%s\n' 'unresolved evidence scope is refused' ;; + unsafe-leaf) printf '%s\n' 'unsafe destination leaf is refused' ;; + legitimate) printf '%s\n' 'legitimate in-scope publication remains exact' ;; + *) return 1 ;; + esac +} + +run_evidence_fixture() { + local id=$1 lab scope validation_scope safe fake_data fake_state outside source relative forbidden + evidence_fixture_description "$id" >/dev/null || return 2 + lab="$TMP/evidence-containment/$id" + scope="$lab/scope" + safe="$scope/safe" + fake_data="$lab/fake-data" + fake_state="$lab/fake-state" + outside="$lab/fake-outside" + mkdir -p "$safe/inside-parent" "$fake_data" "$fake_state" "$outside" + validation_scope=$scope + source="$lab/evidence.md" + printf 'bounded evidence\n' > "$source" + + case "$id" in + absolute) + relative="$fake_data/absolute.md"; forbidden=$relative ;; + traversal) + relative='../fake-data/traversal.md'; forbidden="$fake_data/traversal.md" ;; + symlink-leaf-data) + ln -s "$fake_data/symlink-leaf.md" "$safe/symlink-leaf.md" + relative='safe/symlink-leaf.md'; forbidden="$fake_data/symlink-leaf.md" ;; + symlink-parent) + mkdir "$scope/inside-target" + ln -s "$scope/inside-target" "$safe/symlink-parent" + relative='safe/symlink-parent/evidence.md'; forbidden="$scope/inside-target/evidence.md" ;; + symlink-parent-state) + ln -s "$fake_state" "$scope/state-link" + relative='state-link/evidence.md'; forbidden="$fake_state/evidence.md" ;; + resolved-outside) + mkdir "$safe/canonical-boundary" + EVIDENCE_SWAP_PATH=$(canonical_evidence_dir "$safe/canonical-boundary") + EVIDENCE_SWAP_TARGET=$outside + relative='safe/canonical-boundary/evidence.md'; forbidden="$safe/canonical-boundary/evidence.md" ;; + hard-link) + printf 'hard-link sentinel\n' > "$fake_data/hard-target.md" + ln "$fake_data/hard-target.md" "$safe/hard-link.md" + publish_evidence "$scope" 'safe/hard-link.md' "$source" >/dev/null 2>&1 && return 1 + [ "$(cat "$fake_data/hard-target.md")" = 'hard-link sentinel' ] + return ;; + unresolved-parent) + relative='missing/evidence.md'; forbidden="$scope/missing/evidence.md" ;; + existing) + printf 'existing sentinel\n' > "$safe/existing.md" + publish_evidence "$scope" 'safe/existing.md' "$source" >/dev/null 2>&1 && return 1 + [ "$(cat "$safe/existing.md")" = 'existing sentinel' ] + return ;; + scope-symlink) + ln -s "$scope" "$lab/scope-link" + validation_scope="$lab/scope-link" + relative='safe/scope-symlink.md'; forbidden="$safe/scope-symlink.md" ;; + scope-unresolved) + validation_scope="$lab/missing-scope" + relative='safe/scope-unresolved.md'; forbidden="$lab/missing-scope/safe/scope-unresolved.md" ;; + unsafe-leaf) + publish_evidence "$scope" '..' "$source" >/dev/null 2>&1 && return 1 + return 0 ;; + legitimate) + publish_evidence "$scope" 'safe/legitimate.md' "$source" >/dev/null 2>&1 \ + && cmp -s "$source" "$safe/legitimate.md" + return ;; + esac + publish_evidence "$validation_scope" "$relative" "$source" >/dev/null 2>&1 && return 1 + [ ! -e "$forbidden" ] && [ ! -L "$forbidden" ] +} + +self_test_evidence_containment() { + local id description pass_count=0 fail_count=0 + while IFS= read -r id; do + description=$(evidence_fixture_description "$id") + if run_evidence_fixture "$id"; then + printf ' PASS %s\n' "$description" + pass_count=$((pass_count + 1)) + else + printf ' FAIL %s\n' "$description" + fail_count=$((fail_count + 1)) + fi + done < <(evidence_fixture_ids) + printf 'CONTAINMENT FIXTURES passed=%s failed=%s\n' "$pass_count" "$fail_count" + [ "$pass_count" -eq 13 ] && [ "$fail_count" -eq 0 ] +} + +if [ "$MODE" = fixture ]; then + run_evidence_fixture "$EVIDENCE_FIXTURE" + exit +fi +if [ "$MODE" = containment ]; then + self_test_evidence_containment + exit +fi + +apply_mutation() { + local target=$1 from=$2 to=$3 count_file=$4 + FROM_TEXT=$from TO_TEXT=$to COUNT_FILE=$count_file perl -0pi -e ' + BEGIN { + $from = $ENV{FROM_TEXT}; + $to = $ENV{TO_TEXT}; + $count_file = $ENV{COUNT_FILE}; + } + $count = 0; + $offset = 0; + while (($found = index($_, $from, $offset)) >= 0) { + $count++; + $offset = $found + length($from); + } + if ($count == 1) { + s/\Q$from\E/$to/; + } + open(my $fh, ">", $count_file) or die $!; + print {$fh} "$count\n"; + close($fh) or die $!; + ' "$target" +} + +run_mutation_test() { + local target=$1 output=$2 + FM_MUTATION_RUN=1 TOOL=$target perl -e ' + my $timeout = shift; + my $pid = fork; + die "fork failed" unless defined $pid; + if (!$pid) { + setpgrp(0, 0); + exec @ARGV; + } + local $SIG{ALRM} = sub { + kill "TERM", -$pid; + select undef, undef, undef, 0.2; + kill "KILL", -$pid; + waitpid $pid, 0; + exit 124; + }; + alarm $timeout; + waitpid $pid, 0; + alarm 0; + exit(($? & 127) ? 128 + ($? & 127) : $? >> 8); + ' "${MUTATION_TIMEOUT:-120}" "$TEST" >"$output" 2>&1 +} + +mutant() { + local id=$1 anchor=$2 from=$3 to=$4 claim=$5 target count_file count outcome status + denominator=$((denominator + 1)) + if [ -n "${MUTATION_ONLY:-}" ] && [ "$MUTATION_ONLY" != "$id" ]; then + return + fi + target="$TMP/$id.sh" + count_file="$TMP/$id.count" + cp "$SOURCE" "$target" + apply_mutation "$target" "$from" "$to" "$count_file" + count=$(cat "$count_file") + if [ "$count" -ne 1 ]; then + outcome=VOID + status=- + void=$((void + 1)) + else + set +e + run_mutation_test "$target" "$TMP/$id.out" + status=$? + set -e + if [ "$status" -eq 124 ] || [ "$status" -eq 126 ] || [ "$status" -eq 127 ] || [ "$status" -ge 128 ]; then + outcome=HARNESS-BROKEN + harness_broken=$((harness_broken + 1)) + printf 'HARNESS DETAIL %s status=%s\n' "$id" "$status" >&2 + tail -20 "$TMP/$id.out" >&2 + elif [ "$status" -eq 0 ]; then + outcome=SURVIVED + survived=$((survived + 1)) + else + outcome=KILLED + killed=$((killed + 1)) + fi + fi + printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$id" "$anchor" "$count" "$outcome" "$status" "$claim" >> "$RESULTS" + printf '%s %s substitutions=%s status=%s\n' "$id" "$outcome" "$count" "$status" +} + +mutant M001 strict.errexit 'set -euo pipefail' 'set -uo pipefail' 'Shell failures cannot fall through.' +mutant M002 manifest.denominator 'BUNDLE_MEMBER_COUNT=4' 'BUNDLE_MEMBER_COUNT=5' 'The bundle denominator is exactly four.' +mutant M003 canonical.exists '[ -d "$1" ] || die "ledger directory does not exist: $1"' ':' 'Only an existing directory can be canonicalized.' +mutant M004 canonical.physical 'pwd -P' 'pwd -L' 'Admission returns physical canonical paths.' +mutant M005 manifest.tests-member 'CONTRACT.md fm-sovereign-ledger.sh ledger.tsv tests.sh' 'CONTRACT.md fm-sovereign-ledger.sh ledger.tsv tests.missing' 'The exact manifest includes tests.sh.' +mutant M006 enumerate.top-level '-mindepth 1 -maxdepth 1' '-mindepth 2 -maxdepth 2' 'Every real top-level entry name is enumerated.' +mutant M007 enumerate.real-names 'printf "%s\n" "${path##*/}"' 'printf "%s\n" CONTRACT.md fm-sovereign-ledger.sh ledger.tsv tests.sh' 'Enumeration retains real entry names.' +mutant M008 enumerate.exact-set '[ "$entries" = "$required" ]' 'true' 'Real names must equal the manifest byte-for-byte.' +mutant M009 require.no-symlink '[ -L "$dir/$entry" ] && die "ledger bundle contains a symlink: $dir/$entry"' ':' 'Manifest members cannot be symlinks.' +mutant M010 require.regular '[ -f "$dir/$entry" ] || die "ledger bundle contains a non-regular file: $dir/$entry"' ':' 'Manifest members must remain regular files.' +mutant M011 require.executable '[ -x "$dir/fm-sovereign-ledger.sh" ] || die "ledger verifier is not executable: $dir/fm-sovereign-ledger.sh"' ':' 'The verifier remains executable.' +mutant M012 bytes.reject '|| die "replica differs from primary: $entry"' '|| :' 'Compared bytes must match.' +mutant M013 device.bsd-read "stat -f '%d'" "printf '1'" 'BSD st_dev is read from stat.' +mutant M014 device.gnu-read "stat -c '%d'" "printf '1'" 'GNU st_dev is read from stat.' +mutant M015 device.numeric-shape "'^[0-9]+$'" "'.*'" 'Only numeric st_dev identities are accepted.' +mutant M016 containment.distinct '[ "$primary" != "$replica" ] || die "primary and replica directories must differ"' ':' 'Canonical pair paths must differ.' +mutant M017 containment.primary 'case "$primary/" in "$replica/"*) die "primary and replica directories must not contain one another" ;; esac' ':' 'Primary cannot be contained by replica.' +mutant M018 containment.replica 'case "$replica/" in "$primary/"*) die "primary and replica directories must not contain one another" ;; esac' ':' 'Replica cannot be contained by primary.' +mutant M019 device.primary-read 'primary_device=$(portable_device_identity "$primary")' 'primary_device=1' 'Admission reads primary st_dev.' +mutant M020 device.replica-read 'replica_device=$(portable_device_identity "$replica")' 'replica_device=2' 'Admission reads replica st_dev.' +mutant M021 device.inequality '[ "$primary_device" = "$replica_device" ]' 'false' 'Equal st_dev is refused by default.' +mutant M022 device.optout-only '[ "$ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY" != yes ]' 'true' 'Only the named opt-out waives device inequality.' +mutant M023 verifier.execute 'LEDGER_DIR="$subject" "$primary/fm-sovereign-ledger.sh" verify >/dev/null' ':' 'Primary verifier establishes ledger validity.' +mutant M024 prefix.nonempty '[ "$replica_lines" -gt 0 ]' 'true' 'An empty ledger is not a prefix.' +mutant M025 prefix.shorter '[ "$primary_lines" -gt "$replica_lines" ]' 'true' 'A prefix is strictly shorter.' +mutant M026 prefix.leading 'head -n "$replica_lines"' 'tail -n "$replica_lines"' 'Prefix comparison uses leading records.' +mutant M027 recheck.replica-type $'require_bundle "$primary"\n require_bundle "$replica"\n admit_device_pair "$primary" "$replica"\n admit_independent_bundle_members "$primary" "$replica"\n compare_files' $'require_bundle "$primary"\n :\n admit_device_pair "$primary" "$replica"\n admit_independent_bundle_members "$primary" "$replica"\n compare_files' 'Admission reclassifies replica entries after verifier execution.' +mutant M028 directory.canonical-stable '[ "$canonical" = "$path" ] || die "$label directory no longer resolves to its admitted path: $path"' ':' 'A directory path cannot change after admission.' +mutant M029 directory.identity-stable '[ "$identity" = "$expected" ] || die "$label directory changed after admission: $path"' ':' 'A directory object cannot change after admission.' +mutant M030 publish.exclusive-create 'O_WRONLY | O_CREAT | O_EXCL' 'O_WRONLY | O_CREAT' 'Bundle members are created exclusively.' +mutant M031 publish.preserve-mode 'chmod($source_stat[2] & 07777, $output)' 'chmod(0600, $output)' 'Exclusive copy preserves required modes.' +mutant M032 publish.parent-no-symlink '[ ! -L "$parent" ] || die "replica parent directory is symlinked: $parent"' ':' 'Snapshot parent cannot be a symlink.' +mutant M033 publish.safe-leaf "case \"\$base\" in ''|.|..) die \"unsafe replica destination leaf: \$replica_input\" ;; esac" ':' 'Snapshot leaf is safe.' +mutant M034 publish.destination-absent '[ ! -e "$replica" ] && [ ! -L "$replica" ]' 'true' 'Snapshot destination is absent and not symlinked.' +mutant M035 publish.parent-device 'admit_device_pair "$primary" "$parent"' ':' 'Snapshot checks destination-volume st_dev before creation.' +mutant M036 publish.mkdir-exclusive 'mkdir -- "$replica" || die "could not exclusively create replica destination: $replica"' 'mkdir -p -- "$replica"' 'Snapshot claims its final destination exclusively.' +mutant M037 publish.copy-bundle 'copy_bundle "$primary" "$replica"' ':' 'Snapshot populates the admitted destination.' +mutant M038 publish.validate-object 'admit_existing_pair "$primary" "$replica" exact' ':' 'Snapshot validates the object it published.' +mutant M039 refresh.prefix-admission 'admit_pair "$1" "$2" prefix' 'admit_pair "$1" "$2" inspect' 'Refresh proves the prefix before writing.' +mutant M040 refresh.atomic-copy 'copy_ledger_atomically "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA"' ':' 'Refresh publishes the admitted ledger update.' +mutant M041 refresh.post-admission $' copy_ledger_atomically "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA"\n admit_pair "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA" exact\n printf \'REFRESH PASS (replica advanced to primary)\\n\'' $' copy_ledger_atomically "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA"\n :\n printf \'REFRESH PASS (replica advanced to primary)\\n\'' 'Refresh re-admits the published exact pair.' +mutant M042 verify.inspect-admission 'admit_pair "$1" "$2" inspect' 'admit_pair "$1" "$2" exact' 'Verify admits stale pairs before classifying them.' +mutant M043 verify.exact-recheck $' admit_pair "$ADMITTED_PRIMARY" "$ADMITTED_REPLICA" exact\n if [ "$ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY" = yes ]; then' $' :\n if [ "$ALLOW_SAME_VOLUME_WITHOUT_DEVICE_REDUNDANCY" = yes ]; then' 'Verify re-admits an exact pair before PASS.' +mutant M044 option.named-flag 'if [ "${1:-}" = --allow-same-volume-without-device-redundancy ]; then' 'if [ "${1:-}" = --allow-same-volume ]; then' 'The opt-out name states the surrendered property.' +mutant M045 dispatch.snapshot 'snapshot) shift; [ "$#" -eq 2 ] || usage; cmd_snapshot "$@"' 'snapshot) shift; [ "$#" -eq 2 ] || usage; :' 'Dispatch cannot bypass snapshot admission.' +mutant M046 dispatch.refresh 'refresh) shift; [ "$#" -eq 2 ] || usage; cmd_refresh "$@"' 'refresh) shift; [ "$#" -eq 2 ] || usage; :' 'Dispatch cannot bypass refresh admission.' +mutant M047 dispatch.verify 'verify) shift; [ "$#" -eq 2 ] || usage; cmd_verify "$@"' 'verify) shift; [ "$#" -eq 2 ] || usage; :' 'Dispatch cannot bypass verify admission.' +mutant M048 identity.member-read $'portable_member_identity() {\n portable_directory_identity "$1"\n}' $'portable_member_identity() {\n printf "1:1\\n"\n}' 'Member identity is read from the admitted filesystem object.' +mutant M049 identity.cross-product-disjoint '[ "$primary_identity" != "$replica_identity" ]' 'true' 'Every primary identity must differ from every replica identity.' +mutant M050 identity.cross-product-denominator '[ "$compared" -eq $((BUNDLE_MEMBER_COUNT * BUNDLE_MEMBER_COUNT)) ]' 'true' 'The complete four-by-four member cross-product is proved.' +mutant M051 admission.initial-member-identity $' admit_device_pair "$primary" "$replica"\n require_bundle "$primary"\n require_bundle "$replica"\n admit_independent_bundle_members "$primary" "$replica"\n compare_files' $' admit_device_pair "$primary" "$replica"\n require_bundle "$primary"\n require_bundle "$replica"\n :\n compare_files' 'Initial pair admission proves member independence before verifier execution.' +mutant M052 admission.final-member-identity $' require_bundle "$primary"\n require_bundle "$replica"\n admit_device_pair "$primary" "$replica"\n admit_independent_bundle_members "$primary" "$replica"\n compare_files' $' require_bundle "$primary"\n require_bundle "$replica"\n admit_device_pair "$primary" "$replica"\n :\n compare_files' 'Final pair admission re-proves member independence after verifier execution.' +mutant M053 refresh.carried-member-identity ' require_admitted_bundle_members "$primary" "$replica"' ' :' 'Refresh rechecks the admitted member identities before publication.' + +[ "$denominator" -eq 53 ] || { printf 'invalid owned denominator: %s\n' "$denominator" >&2; exit 1; } + +control="$TMP/CONTROL.sh" +control_count="$TMP/CONTROL.count" +cp "$SOURCE" "$control" +apply_mutation "$control" 'make, advance' 'make, advance' "$control_count" +control_substitutions=$(cat "$control_count") +set +e +run_mutation_test "$control" "$TMP/CONTROL.out" +control_status=$? +set -e +if [ "$control_substitutions" -eq 1 ] && [ "$control_status" -eq 0 ]; then + control_outcome=SURVIVED +else +control_outcome=FAILED +fi +printf 'CONTROL %s substitutions=%s status=%s\n' "$control_outcome" "$control_substitutions" "$control_status" + +PUBLICATION_MATRIX="$TMP/evidence-publication-matrix.md" +"$ROOT/tests/fm-sovereign-ledger-evidence-publish.mutation.sh" --markdown > "$PUBLICATION_MATRIX" +EVIDENCE_STAGE="$TMP/evidence.md" +{ + printf '# Sovereign ledger redundancy mutation evidence\n\n' + printf 'Audience: maintainer verification.\n\n' + printf 'Verified on 2026-08-15 against the R5 sovereign-ledger redundancy chokepoint.\n' + printf 'The owned denominator spans exact real-name enumeration, the st_dev predicate, the four-by-four member-identity cross-product, both member admission calls, carried-identity refresh recheck, exclusive publication, verification, and all three dispatch entries.\n' + printf 'Every run copied the implementation under one scoped `mktemp -d`; the live ledger, `data/`, and `state/` were never inputs or targets.\n\n' + printf '```sh\n' + printf 'tests/fm-sovereign-ledger-redundancy.mutation.sh --write-evidence sovereign-ledger-redundancy-mutation.candidate.md\n' + printf '```\n\n' + printf 'Evidence publication rejects existing destinations, so the candidate is reviewed against this record before replacement rather than overwriting it in place.\n\n' + printf 'Observed summary: `killed=%s survived=%s void=%s harness_broken=%s denominator=%s`; the intentional no-op control recorded `substitutions=%s status=%s outcome=%s`.\n\n' "$killed" "$survived" "$void" "$harness_broken" "$denominator" "$control_substitutions" "$control_status" "$control_outcome" + printf '| Mutant | Stable exact clause anchor | Substitutions | Outcome | Status | Unenforced claim when survived |\n' + printf '| --- | --- | ---: | --- | ---: | --- |\n' + while IFS=$'\t' read -r id anchor count outcome status claim; do + if [ "$outcome" != SURVIVED ]; then claim=-; fi + printf '| `%s` | `%s` | %s | %s | %s | %s |\n' "$id" "$anchor" "$count" "$outcome" "$status" "$claim" + done < "$RESULTS" + cat <<'EVIDENCE_PUBLICATION' + +## Evidence publication containment + +The complete fixture results, cell outcomes, alternate defenders, per-mutant totals, and aggregate denominator below come from one canonical generated stream. +EVIDENCE_PUBLICATION + printf '\n' + cat "$PUBLICATION_MATRIX" +} > "$EVIDENCE_STAGE" + +printf 'MUTATION SUMMARY killed=%s survived=%s void=%s harness_broken=%s denominator=%s\n' "$killed" "$survived" "$void" "$harness_broken" "$denominator" +[ -s "$EVIDENCE_STAGE" ] || { printf 'REFUSED: generated mutation evidence is empty\n' >&2; exit 1; } +[ "$(wc -l < "$RESULTS" | tr -d '[:space:]')" -eq "$denominator" ] \ + || { printf 'REFUSED: mutation result rows do not match denominator\n' >&2; exit 1; } +awk -F '\t' 'NF != 6 || $3 != 1 || ($4 != "KILLED" && $4 != "SURVIVED") { exit 1 }' "$RESULTS" \ + || { printf 'REFUSED: every mutant must record one substitution and an individual outcome\n' >&2; exit 1; } +[ "$void" -eq 0 ] +[ "$harness_broken" -eq 0 ] +[ "$control_substitutions" -eq 1 ] +[ "$control_outcome" = SURVIVED ] +if [ "$REFRESH_EVIDENCE" -eq 1 ]; then + maintained="$ROOT/docs/verification/sovereign-ledger-redundancy-mutation.md" + install_stage=$(mktemp "$ROOT/docs/verification/.sovereign-ledger-redundancy-mutation.XXXXXX") \ + || { printf 'REFUSED: could not stage maintained mutation evidence\n' >&2; exit 1; } + cp "$EVIDENCE_STAGE" "$install_stage" \ + || { rm -f -- "$install_stage"; printf 'REFUSED: could not copy maintained mutation evidence\n' >&2; exit 1; } + if ! [ -s "$install_stage" ] || ! cmp -s "$EVIDENCE_STAGE" "$install_stage"; then + rm -f -- "$install_stage" + printf 'REFUSED: staged maintained mutation evidence is empty or differs\n' >&2 + exit 1 + fi + mv -f -- "$install_stage" "$maintained" \ + || { rm -f -- "$install_stage"; printf 'REFUSED: could not atomically install maintained mutation evidence\n' >&2; exit 1; } + [ -s "$maintained" ] || { printf 'REFUSED: installed maintained mutation evidence is empty\n' >&2; exit 1; } +elif [ -n "$EVIDENCE" ]; then + publish_evidence "$ROOT/docs/verification" "$EVIDENCE" "$EVIDENCE_STAGE" +elif [ "$EMIT_EVIDENCE" -eq 1 ]; then + cat "$EVIDENCE_STAGE" >&3 +elif [ "$VERIFY_EVIDENCE" -eq 1 ]; then + maintained="$ROOT/docs/verification/sovereign-ledger-redundancy-mutation.md" + [ -s "$maintained" ] || { printf 'REFUSED: maintained mutation evidence is empty\n' >&2; exit 1; } + cmp -s "$EVIDENCE_STAGE" "$maintained" || { + diff -u "$maintained" "$EVIDENCE_STAGE" >&2 || true + exit 1 + } +fi diff --git a/tests/fm-sovereign-ledger-redundancy.test.sh b/tests/fm-sovereign-ledger-redundancy.test.sh new file mode 100755 index 0000000000..2a27583d16 --- /dev/null +++ b/tests/fm-sovereign-ledger-redundancy.test.sh @@ -0,0 +1,691 @@ +#!/usr/bin/env bash +# Verify a complete independent replica, append-only refresh, and source-loss recovery through public commands. +# Generated fixture scripts intentionally keep shell expressions literal. +# shellcheck disable=SC2016 +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TOOL=${TOOL:-"$ROOT/bin/fm-sovereign-ledger-redundancy.sh"} +FIXTURE="$ROOT/tests/fixtures/sovereign-ledger-redundancy" +TMP="$(mktemp -d)" +bind_mount_active=0 +bind_mount_target= +directory_hardlink_active=0 +directory_hardlink_target= +cleanup() { + local cleanup_failed=0 + if [ "$bind_mount_active" -eq 1 ]; then + if ! umount "$bind_mount_target" >/dev/null 2>&1; then + printf ' FAIL cleanup could not detach fixture bind mount; retained scratch at %s\n' "$TMP" >&2 + cleanup_failed=1 + fi + fi + if [ "$directory_hardlink_active" -eq 1 ]; then + if ! unlink "$directory_hardlink_target" >/dev/null 2>&1; then + printf ' FAIL cleanup could not unlink fixture directory hard link; retained scratch at %s\n' "$TMP" >&2 + cleanup_failed=1 + fi + fi + if [ "$cleanup_failed" -eq 0 ]; then + rm -rf "$TMP" + fi + return "$cleanup_failed" +} +trap cleanup EXIT +SAME_VOLUME_TOOL="$TMP/fm-sovereign-ledger-same-volume" +export TOOL +printf '%s\n' '#!/usr/bin/env bash' 'exec "$TOOL" --allow-same-volume-without-device-redundancy "$@"' > "$SAME_VOLUME_TOOL" +chmod +x "$SAME_VOLUME_TOOL" +pass=0 +fail=0 +not_verifiable=0 + +ok() { printf ' PASS %s\n' "$1"; pass=$((pass + 1)); } +bad() { printf ' FAIL %s\n' "$1"; fail=$((fail + 1)); } +not_verifiable() { + printf ' NOT_VERIFIABLE %s\n' "$1" + not_verifiable=$((not_verifiable + 1)) +} + +check_ok() { + local description=$1 + shift + if "$@" >/dev/null 2>&1; then ok "$description"; else bad "$description"; fi +} + +check_fails() { + local description=$1 + shift + if "$@" >/dev/null 2>&1; then bad "$description (it SUCCEEDED - guard absent)"; else ok "$description"; fi +} + +check_fails_with() { + local description=$1 expected=$2 output task_rc + shift 2 + set +e + output=$("$@" 2>&1) + task_rc=$? + set -e + if [ "$task_rc" -ne 0 ] && printf '%s\n' "$output" | grep -Fq "$expected"; then + ok "$description" + else + bad "$description (missing expected refusal: $expected; output: ${output//$'\n'/ | })" + fi +} + +check_refuses_promptly_with() { + local description=$1 expected=$2 timeout=$3 output_file task_rc output + shift 3 + output_file="$TMP/bounded-command-$pass-$fail.out" + set +e + perl -e ' + my $timeout = shift; + my $pid = fork; + die "fork failed" unless defined $pid; + if (!$pid) { + setpgrp(0, 0); + exec @ARGV; + } + local $SIG{ALRM} = sub { + kill "TERM", -$pid; + select undef, undef, undef, 0.1; + kill "KILL", -$pid; + waitpid $pid, 0; + exit 124; + }; + alarm $timeout; + waitpid $pid, 0; + alarm 0; + exit(($? & 127) ? 128 + ($? & 127) : $? >> 8); + ' "$timeout" "$@" > "$output_file" 2>&1 + task_rc=$? + set -e + output=$(cat "$output_file") + if [ "$task_rc" -ne 0 ] && [ "$task_rc" -ne 124 ] && printf '%s\n' "$output" | grep -Fq "$expected"; then + ok "$description" + elif [ "$task_rc" -eq 124 ]; then + bad "$description (timed out without the observed refusal: $expected)" + else + bad "$description (missing expected refusal: $expected; output: ${output//$'\n'/ | })" + fi +} + +add_ruling() { + local dir=$1 source_dir=$2 number=$3 source text + source="$source_dir/ruling-$number.md" + printf '%s\n' "# ruling-$number" '' '**Decided by:** the captain' '' "Exact ruling-$number text." > "$source" + text=$(base64 < "$source" | tr -d '\n') + printf 'ruling-%s\t%s\t%s\n' "$number" "$source" "$text" >> "$dir/ledger.tsv" +} + +make_bundle() { + local dir=$1 source_dir=$2 number + mkdir -p "$dir" "$source_dir" + cp "$FIXTURE/CONTRACT.md" "$dir/CONTRACT.md" + cp "$FIXTURE/fm-sovereign-ledger.sh" "$dir/fm-sovereign-ledger.sh" + cp "$FIXTURE/tests.sh" "$dir/tests.sh" + chmod +x "$dir/fm-sovereign-ledger.sh" "$dir/tests.sh" + : > "$dir/ledger.tsv" + for number in 1 2 3 4; do add_ruling "$dir" "$source_dir" "$number"; done +} + +PRIMARY="$TMP/primary" +REPLICA="$TMP/replica" +make_bundle "$PRIMARY" "$TMP/sources" + +echo 'T1 complete, independent bundle preconditions' +check_fails_with 'verify REFUSES a nonexistent ledger directory' 'ledger directory does not exist' "$SAME_VOLUME_TOOL" verify "$TMP/absent-primary" "$TMP/absent-replica" +mkdir -p "$TMP/missing-primary" +check_fails_with 'snapshot REFUSES an incomplete primary bundle' 'ledger bundle manifest differs from the exact required names' "$SAME_VOLUME_TOOL" snapshot "$TMP/missing-primary" "$TMP/missing-replica" +MISSING_CONTRACT="$TMP/missing-contract" +mkdir -p "$MISSING_CONTRACT" +cp "$PRIMARY/ledger.tsv" "$MISSING_CONTRACT/ledger.tsv" +cp "$PRIMARY/fm-sovereign-ledger.sh" "$MISSING_CONTRACT/fm-sovereign-ledger.sh" +check_fails_with 'snapshot REFUSES a primary bundle without its contract' 'ledger bundle manifest differs from the exact required names' "$SAME_VOLUME_TOOL" snapshot "$MISSING_CONTRACT" "$TMP/missing-contract-replica" +check_fails_with 'snapshot REFUSES the same primary and replica directory' 'primary and replica directories must differ' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$PRIMARY" +check_ok 'snapshot atomically CREATES the complete second ledger bundle' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$REPLICA" +check_ok 'verify PASSES for the exact independent replica' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" + +EXECUTABLE="$TMP/non-executable-replica" +check_ok 'snapshot CREATES an executable-bit fixture bundle' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$EXECUTABLE" +chmod -x "$EXECUTABLE/fm-sovereign-ledger.sh" +check_fails_with 'verify REFUSES a non-executable replica verifier' 'ledger verifier is not executable' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$EXECUTABLE" +chmod +x "$EXECUTABLE/fm-sovereign-ledger.sh" +FAILING_FIND_BIN="$TMP/failing-find-bin" +mkdir "$FAILING_FIND_BIN" +printf '%s\n' '#!/usr/bin/env bash' 'exit 71' > "$FAILING_FIND_BIN/find" +chmod +x "$FAILING_FIND_BIN/find" +check_fails_with 'verify REFUSES when exact bundle enumeration fails' 'could not enumerate ledger bundle' env PATH="$FAILING_FIND_BIN:$PATH" "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" + +echo 'T2 staleness is loud and refresh accepts only an append-only prefix' +add_ruling "$PRIMARY" "$TMP/sources" 5 +check_fails_with 'verify names a verified stale replica and its refresh remedy' 'replica is a verified stale prefix; run refresh' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" +check_ok 'refresh advances the verified append-only replica' "$SAME_VOLUME_TOOL" refresh "$PRIMARY" "$REPLICA" +check_ok 'verify PASSES after refresh' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" +check_fails_with 'refresh REFUSES an equal-length replica rather than treating it as stale' 'replica is not a verified byte-exact append-only prefix of primary' "$SAME_VOLUME_TOOL" refresh "$PRIMARY" "$REPLICA" +NONPREFIX="$TMP/non-prefix-replica" +check_ok 'snapshot CREATES a non-prefix refresh fixture' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$NONPREFIX" +{ sed -n '2p' "$NONPREFIX/ledger.tsv"; sed -n '1p' "$NONPREFIX/ledger.tsv"; sed -n '3,$p' "$NONPREFIX/ledger.tsv"; } > "$NONPREFIX/reordered-ledger.tsv" +mv "$NONPREFIX/reordered-ledger.tsv" "$NONPREFIX/ledger.tsv" +check_fails_with 'refresh REFUSES a verifying replica that is not a byte-exact prefix' 'replica is not a verified byte-exact append-only prefix of primary' "$SAME_VOLUME_TOOL" refresh "$PRIMARY" "$NONPREFIX" +SHORT_NONPREFIX="$TMP/short-non-prefix-replica" +check_ok 'snapshot CREATES a shorter non-prefix refresh fixture' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$SHORT_NONPREFIX" +{ sed -n '2p' "$SHORT_NONPREFIX/ledger.tsv"; sed -n '1p' "$SHORT_NONPREFIX/ledger.tsv"; sed -n '3,4p' "$SHORT_NONPREFIX/ledger.tsv"; } > "$SHORT_NONPREFIX/reordered-ledger.tsv" +mv "$SHORT_NONPREFIX/reordered-ledger.tsv" "$SHORT_NONPREFIX/ledger.tsv" +check_fails_with 'refresh REFUSES a shorter verifying replica that is not byte-exact' 'replica is not a verified byte-exact append-only prefix of primary' "$SAME_VOLUME_TOOL" refresh "$PRIMARY" "$SHORT_NONPREFIX" +EMPTY_PREFIX_PRIMARY="$TMP/empty-prefix-primary" +EMPTY_PREFIX_REPLICA="$TMP/empty-prefix-replica" +make_bundle "$EMPTY_PREFIX_PRIMARY" "$TMP/empty-prefix-sources" +printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$EMPTY_PREFIX_PRIMARY/fm-sovereign-ledger.sh" +chmod +x "$EMPTY_PREFIX_PRIMARY/fm-sovereign-ledger.sh" +check_ok 'snapshot CREATES an empty-prefix guard fixture' "$SAME_VOLUME_TOOL" snapshot "$EMPTY_PREFIX_PRIMARY" "$EMPTY_PREFIX_REPLICA" +: > "$EMPTY_PREFIX_REPLICA/ledger.tsv" +GNU_HEAD_BIN="$TMP/gnu-head-bin" +mkdir "$GNU_HEAD_BIN" +# shellcheck disable=SC2016 +printf '%s\n' '#!/usr/bin/env bash' 'if [ "${1:-}" = -n ] && [ "${2:-}" = 0 ]; then exit 0; fi' 'exec /usr/bin/head "$@"' > "$GNU_HEAD_BIN/head" +chmod +x "$GNU_HEAD_BIN/head" +check_refuses_promptly_with 'refresh promptly REFUSES an empty replica ledger under GNU head semantics even when the fixture verifier accepts it' 'replica is not a verified byte-exact append-only prefix of primary' 3 env PATH="$GNU_HEAD_BIN:$PATH" "$SAME_VOLUME_TOOL" refresh "$EMPTY_PREFIX_PRIMARY" "$EMPTY_PREFIX_REPLICA" + +if [ "${FM_MUTATION_RUN:-0}" != 1 ]; then +echo 'T3 source loss leaves the four required fixture rulings provable' +for number in 1 2 3 4; do unlink "$TMP/sources/ruling-$number.md"; done +set +e +recheck_output=$(LEDGER_DIR="$PRIMARY" "$PRIMARY/fm-sovereign-ledger.sh" recheck 2>&1) +recheck_status=$? +set -e +if [ "$recheck_status" -ne 0 ] && [ "$(printf '%s\n' "$recheck_output" | grep -c '^SOURCE_GONE')" -eq 4 ] && printf '%s\n' "$recheck_output" | grep -q '^recheck: 5 entries, 4 divergent$'; then + ok 'fixture recheck reports all four removed sources as SOURCE_GONE' +else + bad 'fixture recheck did not expose all four removed sources' +fi +check_ok 'redundancy verify PASSES after the four sources are gone' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" +for number in 1 2 3 4; do + expected="$TMP/ruling-$number.expected" + base64 -d < <(awk -F '\t' -v key="ruling-$number" '$1 == key { print $3 }' "$PRIMARY/ledger.tsv") > "$expected" + if diff -q <(LEDGER_DIR="$REPLICA" "$REPLICA/fm-sovereign-ledger.sh" text "ruling-$number") "$expected" >/dev/null 2>&1; then + ok "replica returns exact ruling-$number text after source loss" + else + bad "replica did not return exact ruling-$number text after source loss" + fi +done +fi + +echo 'T4 every bundle-file symlink is refused before it can masquerade as a copy' +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + symlink_replica="$TMP/symlink-$entry" + check_ok "snapshot CREATES a $entry symlink fixture" "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$symlink_replica" + unlink "$symlink_replica/$entry" + ln -s "$PRIMARY/$entry" "$symlink_replica/$entry" + check_fails_with "verify REFUSES a symlinked $entry" 'ledger bundle contains a symlink' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$symlink_replica" +done + +echo 'T5 non-regular bundle members are refused before comparison or execution' +NONREGULAR="$TMP/non-regular-replica" +check_ok 'snapshot CREATES a non-regular member fixture bundle' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$NONREGULAR" +unlink "$NONREGULAR/tests.sh" +mkfifo "$NONREGULAR/tests.sh" +check_refuses_promptly_with 'verify promptly REFUSES a FIFO bundle member' 'ledger bundle contains a non-regular file' 3 "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$NONREGULAR" + +echo 'T6 replica bytes are compared before replica-controlled code can execute' +ORDERING="$TMP/ordering-replica" +ORDERING_PROOF="$TMP/replica-code-ran" +check_ok 'snapshot CREATES an ordering fixture bundle' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$ORDERING" +printf '%s\n' '#!/usr/bin/env bash' "touch '$ORDERING_PROOF'" 'exit 0' > "$ORDERING/fm-sovereign-ledger.sh" +chmod +x "$ORDERING/fm-sovereign-ledger.sh" +check_fails 'verify REFUSES a changed replica verifier' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$ORDERING" +if [ ! -e "$ORDERING_PROOF" ]; then ok 'changed replica verifier never executed'; else bad 'changed replica verifier executed before byte comparison'; fi + +echo 'T7 divergence and extra files are detected and never repaired' +printf 'tamper\n' >> "$REPLICA/CONTRACT.md" +check_fails 'verify FAILS when replica contract bytes diverge' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" +check_fails 'snapshot REFUSES to overwrite a divergent replica' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$REPLICA" +cp "$PRIMARY/CONTRACT.md" "$REPLICA/CONTRACT.md" +printf 'planted\n' > "$REPLICA/EXTRA-CONTRACT.md" +check_fails_with 'verify REFUSES an unexpected replica file' 'ledger bundle manifest differs from the exact required names' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" +unlink "$REPLICA/EXTRA-CONTRACT.md" +check_ok 'verify PASSES after fixture restore' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$REPLICA" + +echo 'T8 an invalid primary is never copied' +INVALID="$TMP/invalid-primary" +mkdir -p "$INVALID" +cp "$PRIMARY"/* "$INVALID/" +sed -i.bak '1s/ruling-1/not-a-ruling/' "$INVALID/ledger.tsv" +unlink "$INVALID/ledger.tsv.bak" +INVALID_REPLICA="$TMP/invalid-replica" +check_fails 'snapshot REFUSES a primary rejected by its own verifier' "$SAME_VOLUME_TOOL" snapshot "$INVALID" "$INVALID_REPLICA" +if [ ! -e "$INVALID_REPLICA" ]; then ok 'rejected primary leaves no replica directory behind'; else bad 'rejected primary wrote a replica directory'; fi + +echo 'T9 the st_dev predicate is portable and fail-closed' +IDENTITY_PRIMARY="$TMP/identity-primary" +IDENTITY_REPLICA="$TMP/identity-replica" +IDENTITY_STAT_BIN="$TMP/identity-stat-bin" +check_ok 'snapshot CREATES an identity-check fixture bundle' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$IDENTITY_PRIMARY" +check_ok 'snapshot CREATES an identity-check replica bundle' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$IDENTITY_REPLICA" +mkdir "$IDENTITY_STAT_BIN" +# shellcheck disable=SC2016 +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'set -euo pipefail' \ + 'target="${!#}"' \ + 'case "$target" in' \ + ' *identity-primary|*identity-primary/*) side=primary ;;' \ + ' *identity-replica|*identity-replica/*) side=replica ;;' \ + ' *) exit 70 ;;' \ + 'esac' \ + 'case "$*" in' \ + ' *%d:%i*) if [ "$side" = primary ]; then printf "1:101\\n"; else printf "2:202\\n"; fi ;;' \ + ' *%d*)' \ + ' case "${IDENTITY_MODE:-}" in' \ + ' same) printf "1\\n" ;;' \ + ' distinct) if [ "$side" = primary ]; then printf "1\\n"; else printf "2\\n"; fi ;;' \ + ' invalid) printf "not-a-device\\n" ;;' \ + ' *) exit 71 ;;' \ + ' esac ;;' \ + ' *) exit 72 ;;' \ + 'esac' > "$IDENTITY_STAT_BIN/stat" +chmod +x "$IDENTITY_STAT_BIN/stat" +check_fails_with 'verify REFUSES equal numeric st_dev identities' 'primary and replica must be on different devices' env PATH="$IDENTITY_STAT_BIN:$PATH" IDENTITY_MODE=same "$TOOL" verify "$IDENTITY_PRIMARY" "$IDENTITY_REPLICA" +check_ok 'verify ACCEPTS distinct numeric st_dev identities' env PATH="$IDENTITY_STAT_BIN:$PATH" IDENTITY_MODE=distinct "$TOOL" verify "$IDENTITY_PRIMARY" "$IDENTITY_REPLICA" +check_fails_with 'verify REFUSES a nonnumeric st_dev identity' 'could not establish primary st_dev identity' env PATH="$IDENTITY_STAT_BIN:$PATH" IDENTITY_MODE=invalid "$TOOL" verify "$IDENTITY_PRIMARY" "$IDENTITY_REPLICA" + +echo 'T10 hard links fall to the one device predicate by default' +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + hardlink_replica="$TMP/hardlink-$entry" + check_ok "snapshot CREATES a $entry hard-link fixture" "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$hardlink_replica" + unlink "$hardlink_replica/$entry" + ln "$PRIMARY/$entry" "$hardlink_replica/$entry" + check_fails_with "verify REFUSES a same-device pair containing hard-linked $entry" 'primary and replica must be on different devices' "$TOOL" verify "$PRIMARY" "$hardlink_replica" +done + +if [ "${FM_MUTATION_RUN:-0}" != 1 ]; then +echo 'T11 adversarial ledger paths certify real copies that survive primary destruction' +exercise_destruction_shape() { + local label=$1 component=$2 root primary replica sources expected member number all_members all_rulings + root="$TMP/adversarial-paths/$component" + primary="$root/primary" + replica="$root/replica" + sources="$TMP/adversarial-sources/$label" + expected="$TMP/adversarial-expected/$label" + make_bundle "$primary" "$sources" + mkdir -p "$expected" + for member in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -p "$primary/$member" "$expected/$member" + done + check_ok "snapshot CREATES an independent replica under a $label path" "$SAME_VOLUME_TOOL" snapshot "$primary" "$replica" + check_ok "verify CERTIFIES the independent replica under a $label path" "$SAME_VOLUME_TOOL" verify "$primary" "$replica" + rm -rf -- "$primary" + all_members=true + for member in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + if ! cmp -s "$expected/$member" "$replica/$member"; then all_members=false; fi + done + if "$all_members"; then + ok "all four bundle members survive primary destruction under a $label path" + else + bad "a bundle member did not survive primary destruction under a $label path" + fi + all_rulings=true + for number in 1 2 3 4; do + if ! diff -q <(LEDGER_DIR="$replica" "$replica/fm-sovereign-ledger.sh" text "ruling-$number") "$sources/ruling-$number.md" >/dev/null 2>&1; then + all_rulings=false + fi + done + if "$all_rulings"; then + ok "all four rulings remain byte-exact under a $label path" + else + bad "a ruling was not byte-exact after primary destruction under a $label path" + fi +} + +exercise_destruction_shape 'hash' 'Ledger #1' +exercise_destruction_shape 'unterminated bracket' 'Ledger [1' +exercise_destruction_shape 'space' 'Ledger space' +exercise_destruction_shape 'newline' $'Ledger\nnewline' +exercise_destruction_shape 'unicode' 'Ledger-船长-⚓' +fi + +echo 'T12 the instrument distinguishes independent and non-independent replicas' +CONTROL_ROOT="$TMP/control #ledger" +CONTROL_PRIMARY="$CONTROL_ROOT/primary" +CONTROL_REPLICA="$CONTROL_ROOT/replica" +make_bundle "$CONTROL_PRIMARY" "$CONTROL_ROOT/sources" +mkdir -p "$CONTROL_REPLICA" +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + ln -s "$CONTROL_PRIMARY/$entry" "$CONTROL_REPLICA/$entry" +done +check_fails_with 'positive control REFUSES a fully symlinked replica under a hash path' 'ledger bundle contains a symlink' "$SAME_VOLUME_TOOL" verify "$CONTROL_PRIMARY" "$CONTROL_REPLICA" + +echo 'T13 containment is an explicit property' +CONTAINED_PRIMARY="$TMP/containment-primary" +CONTAINED_REPLICA="$CONTAINED_PRIMARY/replica" +make_bundle "$CONTAINED_PRIMARY" "$TMP/containment-sources" +mkdir -p "$CONTAINED_REPLICA" +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -p "$CONTAINED_PRIMARY/$entry" "$CONTAINED_REPLICA/$entry" +done +check_fails_with 'verify REFUSES a replica contained by the primary explicitly' 'primary and replica directories must not contain one another' "$SAME_VOLUME_TOOL" verify "$CONTAINED_PRIMARY" "$CONTAINED_REPLICA" +rm -rf -- "$CONTAINED_REPLICA" +check_fails_with 'snapshot REFUSES a replica target contained by the primary explicitly' 'primary and replica directories must not contain one another' "$SAME_VOLUME_TOOL" snapshot "$CONTAINED_PRIMARY" "$CONTAINED_REPLICA" +OUTER_REPLICA="$TMP/outer-replica" +INNER_PRIMARY="$OUTER_REPLICA/primary" +make_bundle "$OUTER_REPLICA" "$TMP/outer-sources" +make_bundle "$INNER_PRIMARY" "$TMP/inner-sources" +check_fails_with 'verify REFUSES a primary contained by the replica explicitly' 'primary and replica directories must not contain one another' "$SAME_VOLUME_TOOL" verify "$INNER_PRIMARY" "$OUTER_REPLICA" + +echo 'T14 st_dev reads are fail-closed under BSD and GNU stat semantics' +DIALECT_PRIMARY="$TMP/dialect-primary" +DIALECT_REPLICA="$TMP/dialect-replica" +DIALECT_STAT_BIN="$TMP/dialect-stat-bin" +check_ok 'snapshot CREATES a stat-dialect primary fixture' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$DIALECT_PRIMARY" +check_ok 'snapshot CREATES a stat-dialect replica fixture' "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$DIALECT_REPLICA" +mkdir "$DIALECT_STAT_BIN" +cat > "$DIALECT_STAT_BIN/stat" <<'STAT_STUB' +#!/usr/bin/env bash +set -euo pipefail +emit_identity() { + local format=$1 target=$2 device inode + case "$target" in + *dialect-primary|*dialect-primary/*) device=11; inode=101 ;; + *dialect-replica|*dialect-replica/*) device=22; inode=202 ;; + *) exit 70 ;; + esac + case "$format" in + %d) printf '%s\n' "$device" ;; + %d:%i) printf '%s:%s\n' "$device" "$inode" ;; + *) exit 71 ;; + esac +} +case "${STAT_FLAVOUR:-}" in + bsd) + if [ "$#" -eq 3 ] && [ "$1" = -f ]; then + emit_identity "$2" "$3" + elif [ "$#" -eq 4 ] && [ "$1" = -L ] && [ "$2" = -f ]; then + emit_identity "$3" "$4" + else + exit 64 + fi + ;; + gnu) + if { [ "$#" -eq 3 ] && [ "$1" = -f ]; } || { [ "$#" -eq 4 ] && [ "$1" = -L ] && [ "$2" = -f ]; }; then + printf ' File: "%s"\n' "${!#}" + exit 1 + elif [ "$#" -eq 3 ] && [ "$1" = -c ]; then + emit_identity "$2" "$3" + elif [ "$#" -eq 4 ] && [ "$1" = -L ] && [ "$2" = -c ]; then + emit_identity "$3" "$4" + else + exit 64 + fi + ;; + invalid) + printf 'identity unavailable for %s\n' "${!#}" + ;; + *) exit 65 ;; +esac +STAT_STUB +chmod +x "$DIALECT_STAT_BIN/stat" +for flavour in bsd gnu; do + check_ok "verify PASSES distinct st_dev identities with $flavour stat semantics" env PATH="$DIALECT_STAT_BIN:$PATH" STAT_FLAVOUR="$flavour" "$TOOL" verify "$DIALECT_PRIMARY" "$DIALECT_REPLICA" +done +check_fails_with 'verify REFUSES when stat cannot establish a numeric st_dev identity' 'could not establish primary st_dev identity' env PATH="$DIALECT_STAT_BIN:$PATH" STAT_FLAVOUR=invalid "$TOOL" verify "$DIALECT_PRIMARY" "$DIALECT_REPLICA" + +echo 'T15 the complete replica and primary device sets must be disjoint' +CROSS_PRIMARY="$TMP/cross-primary" +CROSS_REPLICA="$TMP/cross-replica" +make_bundle "$CROSS_PRIMARY" "$TMP/cross-sources" +cp "$CROSS_PRIMARY/CONTRACT.md" "$CROSS_PRIMARY/tests.sh" +chmod +x "$CROSS_PRIMARY/tests.sh" +check_ok 'snapshot CREATES a cross-member identity fixture' "$SAME_VOLUME_TOOL" snapshot "$CROSS_PRIMARY" "$CROSS_REPLICA" +unlink "$CROSS_REPLICA/CONTRACT.md" +ln "$CROSS_PRIMARY/tests.sh" "$CROSS_REPLICA/CONTRACT.md" +check_fails_with 'verify REFUSES cross-member shared storage by the device predicate' 'primary and replica must be on different devices' "$TOOL" verify "$CROSS_PRIMARY" "$CROSS_REPLICA" + +echo 'T16 round 5 attacks replace the independence predicate rather than extending it' +R5_PRIMARY="$TMP/r5-primary" +R5_COPY="$TMP/r5-copy" +make_bundle "$R5_PRIMARY" "$TMP/r5-sources" +mkdir "$R5_COPY" +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -p "$R5_PRIMARY/$entry" "$R5_COPY/$entry" +done +check_fails_with 'verify REFUSES a same-volume real copy by default' 'primary and replica must be on different devices' "$TOOL" verify "$R5_PRIMARY" "$R5_COPY" +check_ok 'verify explicit opt-out ACCEPTS a same-volume real copy' "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_COPY" + +if cp -c "$R5_PRIMARY/CONTRACT.md" "$TMP/r5-clone-probe" 2>/dev/null; then + R5_CLONE="$TMP/r5-clone" + mkdir "$R5_CLONE" + for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -c "$R5_PRIMARY/$entry" "$R5_CLONE/$entry" + done + check_fails_with 'verify REFUSES an APFS clone bundle by the device predicate' 'primary and replica must be on different devices' "$TOOL" verify "$R5_PRIMARY" "$R5_CLONE" +else + not_verifiable 'APFS clone attack: cp -c is unavailable on this filesystem; no nearby copy case substituted' +fi + +R5_BIND_TARGET="$TMP/r5-bind-target" +mkdir "$R5_BIND_TARGET" +bind_mount_target=$R5_BIND_TARGET +if mount --bind "$R5_PRIMARY" "$R5_BIND_TARGET" >/dev/null 2>&1 \ + || mount -t nullfs "$R5_PRIMARY" "$R5_BIND_TARGET" >/dev/null 2>&1; then + bind_mount_active=1 + check_fails_with 'verify REFUSES a bind-mounted replica by the admitted identity set' 'shares storage with primary' \ + "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_BIND_TARGET" + if umount "$R5_BIND_TARGET" >/dev/null 2>&1; then + bind_mount_active=0 + else + bad 'bind-mount fixture could not be detached; scratch will be retained without recursive deletion' + fi +else + not_verifiable 'bind-mount attack: this host grants no unprivileged bind or nullfs mount; no nearby mount case substituted' +fi + +R5_DIRECTORY_HARDLINK="$TMP/r5-directory-hardlink" +if ln "$R5_PRIMARY" "$R5_DIRECTORY_HARDLINK" >/dev/null 2>&1; then + directory_hardlink_active=1 + directory_hardlink_target=$R5_DIRECTORY_HARDLINK + check_fails_with 'verify REFUSES a directory hard link by the admitted member identity set' 'shares storage with primary' \ + "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_DIRECTORY_HARDLINK" + if unlink "$R5_DIRECTORY_HARDLINK" >/dev/null 2>&1; then + directory_hardlink_active=0 + else + bad 'directory-hard-link fixture could not be unlinked; scratch will be retained without recursive deletion' + fi +else + not_verifiable 'directory-hard-link attack: this host refuses unprivileged directory hard-link creation; no symlink case substituted' +fi + +not_verifiable 'firmlink attack: this host exposes no unprivileged fixture-creation API for OS-managed firmlinks; no symlink case substituted' + +R5_CASE="$TMP/r5-case" +mkdir "$R5_CASE" +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -p "$R5_PRIMARY/$entry" "$R5_CASE/$entry" +done +mv "$R5_CASE/CONTRACT.md" "$R5_CASE/contract.md" +check_fails_with 'verify REFUSES a case-folded manifest collision by its real entry name' 'ledger bundle manifest differs from the exact required names' "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_CASE" + +R5_TRAVERSAL="$R5_PRIMARY/../r5-primary" +check_fails_with 'verify REFUSES dot-dot traversal that resolves to the primary itself' 'primary and replica directories must differ' "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_TRAVERSAL" +R5_PARENT_ALIAS="$TMP/r5-primary-parent-alias" +ln -s "$TMP" "$R5_PARENT_ALIAS" +check_fails_with 'verify REFUSES a replica path symlinked to the primary parent' 'primary and replica directories must not contain one another' "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_PARENT_ALIAS" + +R5_REFRESH="$TMP/r5-refresh" +mkdir "$R5_REFRESH" +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -p "$R5_PRIMARY/$entry" "$R5_REFRESH/$entry" +done +head -n 3 "$R5_PRIMARY/ledger.tsv" > "$R5_REFRESH/ledger.tsv" +unlink "$R5_REFRESH/tests.sh" +ln "$R5_PRIMARY/tests.sh" "$R5_REFRESH/tests.sh" +R5_REFRESH_BEFORE=$(cat "$R5_REFRESH/ledger.tsv") +check_fails_with 'refresh REFUSES a same-device pair before publication' 'primary and replica must be on different devices' "$TOOL" refresh "$R5_PRIMARY" "$R5_REFRESH" +if [ "$(cat "$R5_REFRESH/ledger.tsv")" = "$R5_REFRESH_BEFORE" ]; then + ok 'refresh refusal leaves the rejected replica byte-exactly unchanged' +else + bad 'refresh wrote ledger.tsv before refusing the pair' +fi + +R5_NONREGULAR="$TMP/r5-nonregular-after-classification" +mkdir "$R5_NONREGULAR" +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + cp -p "$R5_PRIMARY/$entry" "$R5_NONREGULAR/$entry" +done +R5_FIND_BIN="$TMP/r5-find-bin" +mkdir "$R5_FIND_BIN" +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'set -euo pipefail' \ + ' /usr/bin/find "$@"' \ + 'case "$1" in' \ + '*/r5-nonregular-after-classification)' \ + ' if [ ! -e "$R5_SWAP_MARKER" ]; then' \ + ' : > "$R5_SWAP_MARKER"' \ + ' rm -f "$R5_SWAP_DIR/tests.sh"' \ + ' mkdir "$R5_SWAP_DIR/tests.sh"' \ + ' fi ;;' \ + 'esac' > "$R5_FIND_BIN/find" +chmod +x "$R5_FIND_BIN/find" +check_fails_with 'verify reclassifies a non-regular member appearing after enumeration' 'ledger bundle contains a non-regular file' env PATH="$R5_FIND_BIN:$PATH" R5_SWAP_DIR="$R5_NONREGULAR" R5_SWAP_MARKER="$TMP/r5-swap-done" "$TOOL" --allow-same-volume-without-device-redundancy verify "$R5_PRIMARY" "$R5_NONREGULAR" + +echo 'T17 snapshot publication is bound to the destination created exclusively by admission' +R5_TOC_PRIMARY="$TMP/r5-toc-primary" +R5_TOC_DEST="$TMP/r5-toc-destination" +R5_TOC_OUTSIDE="$TMP/r5-toc-outside" +R5_TOC_READY="$TMP/r5-toc-ready" +R5_TOC_COUNT="$TMP/r5-toc-count" +R5_TOC_REAL="$TMP/r5-toc-real-verifier" +R5_TOC_OUTPUT="$TMP/r5-toc-output" +make_bundle "$R5_TOC_PRIMARY" "$TMP/r5-toc-sources" +mkdir "$R5_TOC_OUTSIDE" +cp -p "$R5_TOC_PRIMARY/fm-sovereign-ledger.sh" "$R5_TOC_REAL" +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'set -euo pipefail' \ + 'count=0' \ + '[ ! -e "$R5_TOC_COUNT" ] || count=$(cat "$R5_TOC_COUNT")' \ + 'count=$((count + 1))' \ + 'printf "%s\\n" "$count" > "$R5_TOC_COUNT"' \ + 'if [ "$count" -eq 2 ]; then' \ + ' : > "$R5_TOC_READY"' \ + ' sleep 1' \ + 'fi' \ + 'exec "$R5_TOC_REAL" "$@"' > "$R5_TOC_PRIMARY/fm-sovereign-ledger.sh" +chmod +x "$R5_TOC_PRIMARY/fm-sovereign-ledger.sh" +set +e +env R5_TOC_COUNT="$R5_TOC_COUNT" R5_TOC_READY="$R5_TOC_READY" R5_TOC_REAL="$R5_TOC_REAL" \ + "$TOOL" --allow-same-volume-without-device-redundancy snapshot "$R5_TOC_PRIMARY" "$R5_TOC_DEST" > "$R5_TOC_OUTPUT" 2>&1 & +R5_TOC_PID=$! +set -e +R5_TOC_ATTEMPT=0 +while [ ! -e "$R5_TOC_READY" ] && [ "$R5_TOC_ATTEMPT" -lt 100 ]; do + sleep 0.02 + R5_TOC_ATTEMPT=$((R5_TOC_ATTEMPT + 1)) +done +if [ ! -e "$R5_TOC_DEST" ] && [ ! -L "$R5_TOC_DEST" ]; then + ln -s "$R5_TOC_OUTSIDE" "$R5_TOC_DEST" +fi +set +e +wait "$R5_TOC_PID" +R5_TOC_STATUS=$? +set -e +if [ "$R5_TOC_STATUS" -eq 0 ] && [ -d "$R5_TOC_DEST" ] && [ ! -L "$R5_TOC_DEST" ] \ + && [ -f "$R5_TOC_DEST/ledger.tsv" ] && [ -z "$(find "$R5_TOC_OUTSIDE" -mindepth 1 -maxdepth 1 -print -quit)" ]; then + ok 'snapshot race cannot redirect publication outside its admitted destination' +else + R5_TOC_DIAGNOSTIC=$(cat "$R5_TOC_OUTPUT") + bad "snapshot publication race escaped admission (status=$R5_TOC_STATUS; output: ${R5_TOC_DIAGNOSTIC//$'\n'/ | })" +fi + +echo 'T18 same-volume waiver preserves member independence and snapshot failure boundaries' +for entry in ledger.tsv CONTRACT.md fm-sovereign-ledger.sh tests.sh; do + waived_hardlink_replica="$TMP/waived-hardlink-$entry" + check_ok "snapshot CREATES a waived $entry hard-link fixture" "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$waived_hardlink_replica" + unlink "$waived_hardlink_replica/$entry" + ln "$PRIMARY/$entry" "$waived_hardlink_replica/$entry" + check_fails_with "same-volume waiver REFUSES a hard-linked $entry" 'shares storage with primary' "$SAME_VOLUME_TOOL" verify "$PRIMARY" "$waived_hardlink_replica" +done + +WAIVED_CROSS_PRIMARY="$TMP/waived-cross-primary" +WAIVED_CROSS_REPLICA="$TMP/waived-cross-replica" +make_bundle "$WAIVED_CROSS_PRIMARY" "$TMP/waived-cross-sources" +cp "$WAIVED_CROSS_PRIMARY/CONTRACT.md" "$WAIVED_CROSS_PRIMARY/tests.sh" +chmod +x "$WAIVED_CROSS_PRIMARY/tests.sh" +check_ok 'snapshot CREATES a waived cross-member fixture' "$SAME_VOLUME_TOOL" snapshot "$WAIVED_CROSS_PRIMARY" "$WAIVED_CROSS_REPLICA" +unlink "$WAIVED_CROSS_REPLICA/CONTRACT.md" +ln "$WAIVED_CROSS_PRIMARY/tests.sh" "$WAIVED_CROSS_REPLICA/CONTRACT.md" +check_fails_with 'same-volume waiver REFUSES cross-member shared storage' 'replica CONTRACT.md shares storage with primary tests.sh' "$SAME_VOLUME_TOOL" verify "$WAIVED_CROSS_PRIMARY" "$WAIVED_CROSS_REPLICA" + +PREFLIGHT_STAT_BIN="$TMP/preflight-stat-bin" +PREFLIGHT_DEST="$TMP/preflight-stat-replica" +mkdir "$PREFLIGHT_STAT_BIN" +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'case "$*" in' \ + ' *%d:%i*) exit 75 ;;' \ + ' *%d*) printf "1\\n" ;;' \ + ' *) exit 76 ;;' \ + 'esac' > "$PREFLIGHT_STAT_BIN/stat" +chmod +x "$PREFLIGHT_STAT_BIN/stat" +check_fails_with 'snapshot REFUSES unsupported directory identity before destination creation' 'could not establish replica parent directory identity before creation' env PATH="$PREFLIGHT_STAT_BIN:$PATH" "$TOOL" --allow-same-volume-without-device-redundancy snapshot "$PRIMARY" "$PREFLIGHT_DEST" +if [ ! -e "$PREFLIGHT_DEST" ] && [ ! -L "$PREFLIGHT_DEST" ]; then + ok 'directory identity preflight leaves no replica destination' +else + bad 'directory identity preflight created a replica destination' +fi + +PARTIAL_PERL_BIN="$TMP/partial-perl-bin" +PARTIAL_DEST="$TMP/retained-partial-replica" +mkdir "$PARTIAL_PERL_BIN" +printf '%s\n' '#!/usr/bin/env bash' 'exit 77' > "$PARTIAL_PERL_BIN/perl" +chmod +x "$PARTIAL_PERL_BIN/perl" +check_fails_with 'snapshot identifies a retained partial after post-creation copy failure' 'partial replica retained without deletion' env PATH="$PARTIAL_PERL_BIN:$PATH" "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$PARTIAL_DEST" +if [ -d "$PARTIAL_DEST" ] && [ ! -L "$PARTIAL_DEST" ]; then + ok 'post-creation failure retains the partial replica directory' +else + bad 'post-creation failure deleted or replaced the partial replica directory' +fi + +SIGNAL_MKDIR_BIN="$TMP/signal-mkdir-bin" +SIGNAL_DEST="$TMP/signal-partial-replica" +SIGNAL_READY="$TMP/signal-mkdir-ready" +SIGNAL_OUTPUT="$TMP/signal-output" +REAL_MKDIR=$(command -v mkdir) +mkdir "$SIGNAL_MKDIR_BIN" +printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'set -euo pipefail' \ + '"$REAL_MKDIR" "$@"' \ + ': > "$SIGNAL_READY"' \ + 'sleep 0.1' > "$SIGNAL_MKDIR_BIN/mkdir" +chmod +x "$SIGNAL_MKDIR_BIN/mkdir" +set +e +env PATH="$SIGNAL_MKDIR_BIN:$PATH" REAL_MKDIR="$REAL_MKDIR" SIGNAL_READY="$SIGNAL_READY" \ + "$SAME_VOLUME_TOOL" snapshot "$PRIMARY" "$SIGNAL_DEST" > "$SIGNAL_OUTPUT" 2>&1 & +SIGNAL_PID=$! +set -e +SIGNAL_ATTEMPT=0 +while [ ! -e "$SIGNAL_READY" ] && [ "$SIGNAL_ATTEMPT" -lt 100 ]; do + sleep 0.02 + SIGNAL_ATTEMPT=$((SIGNAL_ATTEMPT + 1)) +done +if [ -e "$SIGNAL_READY" ]; then kill -TERM "$SIGNAL_PID"; fi +set +e +wait "$SIGNAL_PID" +SIGNAL_STATUS=$? +set -e +if [ "$SIGNAL_STATUS" -eq 143 ] \ + && grep -Fq 'partial replica retained or may exist without deletion' "$SIGNAL_OUTPUT"; then + ok 'TERM reports the retained or possibly-created replica destination' +else + SIGNAL_DIAGNOSTIC=$(cat "$SIGNAL_OUTPUT") + bad "TERM did not report its non-deleting snapshot boundary (status=$SIGNAL_STATUS; output: ${SIGNAL_DIAGNOSTIC//$'\n'/ | })" +fi +if [ -d "$SIGNAL_DEST" ] && [ ! -L "$SIGNAL_DEST" ]; then + ok 'TERM leaves the created partial replica directory in place' +else + bad 'TERM deleted or replaced the created partial replica directory' +fi + +printf '\n%s passed, %s failed, %s not verifiable\n' "$pass" "$fail" "$not_verifiable" +[ "$fail" -eq 0 ]