#!/usr/bin/env bash # Regression coverage for the source-safe local recovery hardware contract. set -Eeuo pipefail readonly REPOSITORY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)" readonly LIBRARY_PATH="${REPOSITORY_ROOT}/scripts/lib/k3s-local-recovery.sh" readonly CONTRACT_PATH="${REPOSITORY_ROOT}/infrastructure/security/k3s/local-recovery.env" readonly VALIDATOR_PATH="${REPOSITORY_ROOT}/scripts/validate/k3s-local-recovery.sh" readonly FEASIBILITY_PATH="${REPOSITORY_ROOT}/scripts/validate/k3s-local-recovery-feasibility.sh" readonly PREPARE_WRAPPER_PATH="${REPOSITORY_ROOT}/scripts/bootstrap/prepare-k3s-local-recovery.sh" readonly OPEN_WRAPPER_PATH="${REPOSITORY_ROOT}/scripts/bootstrap/open-k3s-local-recovery.sh" readonly CLOSE_WRAPPER_PATH="${REPOSITORY_ROOT}/scripts/bootstrap/close-k3s-local-recovery.sh" fail() { printf 'TEST FAILURE: %s\n' "$*" >&2 exit 1 } assert_eq() { local expected="$1" actual="$2" description="$3" [[ "$actual" == "$expected" ]] || fail "$description (expected ${expected}, got ${actual})" } assert_succeeds() { "$@" || fail "expected success: $*" } assert_fails() { if "$@"; then fail "expected failure: $*" fi } task5b_focus_selector_guard() { local focus="${1-}" (( $# == 1 )) || return 2 case "$focus" in ''|gate0|api_state|tty_seam|action1) return 0 ;; *) printf 'TEST FAILURE: unsupported TASK5B_FOCUS: %s\n' "$focus" >&2 return 2 ;; esac } # A nonempty Task 5B selector is closed before fixture creation, sourcing, or # any Task 5A/older/full-suite assertion can run. task5b_focus_selector_guard "${TASK5B_FOCUS:-}" || exit $? task4_review2_focus_selector_guard() { local focus="${1-}" (( $# == 1 )) || return 2 case "$focus" in ''|critical|capture|atomic|show_protected|feas_show_protected|feas_attachment_listing) return 0 ;; *) printf 'TEST FAILURE: unsupported TASK4_REVIEW2_FOCUS: %s\n' "$focus" >&2 return 2 ;; esac } # A Task 1 selector is closed before any optional full-suite branch can run. task4_review2_focus_selector_guard "${TASK4_REVIEW2_FOCUS:-}" || exit $? fixture_root="$(mktemp -d "${TMPDIR:-/tmp}/k3s-local-recovery-test.XXXXXX")" cleanup() { case "$fixture_root" in /tmp/k3s-local-recovery-test.*|"${TMPDIR:-/tmp}"/k3s-local-recovery-test.*) rm -rf -- "$fixture_root" ;; *) fail 'refusing to remove an unexpected fixture directory' ;; esac } trap cleanup EXIT write_fixture() { local name="$1" destination destination="${fixture_root}/${name}" cp -- "$CONTRACT_PATH" "$destination" case "$name" in missing_key.env) sed -i '/^K3SLR_OWNER_GID=/d' "$destination" ;; duplicate_key.env) printf '%s\n' 'K3SLR_OWNER_GID=1000' >>"$destination" ;; unknown_key.env) printf '%s\n' 'K3SLR_UNSUPPORTED=value' >>"$destination" ;; command_substitution.env) printf 'K3SLR_OUTER_MOUNT=$(touch %s)\n' "${fixture_root}/evaluated" >"$destination.tmp" sed '/^K3SLR_OUTER_MOUNT=/d' "$destination" >>"$destination.tmp" mv -- "$destination.tmp" "$destination" ;; relative_device.env) sed -i 's|^K3SLR_RECOVERY_DISK_BY_ID=.*|K3SLR_RECOVERY_DISK_BY_ID=dev/disk/by-id/wwn-0x500a0751e6aa6254|' "$destination" ;; parent_path.env) sed -i 's|^K3SLR_DATABASE_RELATIVE=.*|K3SLR_DATABASE_RELATIVE=HyeonworksRecovery/..|' "$destination" ;; *) fail "unknown fixture: $name" ;; esac printf '%s\n' "$destination" } load_fixture() { _k3slr_load_contract "$(write_fixture "$1")" } # The production change this catches is a missing or unsafe source-only contract # library. The RED run before implementation asserted that source failed. # shellcheck source=/dev/null source "$LIBRARY_PATH" assert_succeeds _k3slr_load_contract "$CONTRACT_PATH" assert_eq /dev/disk/by-id/wwn-0x500a0751e6aa6254 \ "$( _k3slr_config_value "$CONTRACT_PATH" K3SLR_RECOVERY_DISK_BY_ID )" \ 'approved recovery disk identity is available without sourcing config' assert_eq 'K3s Recovery LUKS' "$K3SLR_KEEPASS_ENTRY" 'KeePass entry is library-owned' assert_eq 'luks-header-backup.bin' "$K3SLR_KEEPASS_HEADER_ATTACHMENT" 'header attachment is library-owned' assert_fails _k3slr_config_value "$CONTRACT_PATH" K3SLR_UNSUPPORTED assert_fails load_fixture missing_key.env assert_fails load_fixture duplicate_key.env assert_fails load_fixture unknown_key.env assert_fails load_fixture command_substitution.env [[ ! -e "${fixture_root}/evaluated" ]] || fail 'contract parser evaluated command substitution' assert_fails load_fixture relative_device.env assert_fails load_fixture parent_path.env assert_eq 12884901891 "$( _k3slr_required_bytes 858993460 1 )" '25 percent padding and two phases' assert_fails _k3slr_required_bytes -1 0 assert_fails _k3slr_required_bytes 1 9223372036854775807 assert_fails _k3slr_required_bytes 1844674407370955161 0 assert_succeeds _k3slr_inner_capacity_fits 12884901891 1 858993460 assert_fails _k3slr_inner_capacity_fits 12884901890 1 858993460 assert_fails _k3slr_inner_capacity_fits 12884901891 1x 858993460 assert_succeeds _k3slr_outer_capacity_fits 1000000000000 400000000000 34359738368 assert_fails _k3slr_outer_capacity_fits 1000000000000 250000000000 34359738368 assert_fails _k3slr_outer_capacity_fits 1000 1001 1 validator_device_evidence() { local fixture="$1" local recovery_partition=/dev/fixture-recovery-partition local recovery_partition_parent=/dev/fixture-recovery-disk local recovery_fs_uuid=4EA0196C0C5FA27E recovery_partuuid=4670aa9f-9045-4bce-930d-9e84dfec9f38 local recovery_model=CT1000MX500SSD1 recovery_serial=2306E6AA6254 local recovery_wwn=0x500a0751e6aa6254 k3s_major_minor=8:32 local smart_health=PASSED smart_reallocated=0 smart_pending=0 smart_uncorrectable=0 ntfs_probe=pass case "$fixture" in wrong_by_id_target) recovery_partition_parent=/dev/fixture-wrong-disk ;; wrong_fs_uuid) recovery_fs_uuid=AAAAAAAAAAAAAAAA ;; wrong_partuuid) recovery_partuuid=aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa ;; wrong_model) recovery_model=WRONGMODEL ;; wrong_serial) recovery_serial=WRONGSERIAL ;; wrong_wwn) recovery_wwn=0xaaaaaaaaaaaaaaaa ;; same_parent_disk) k3s_major_minor=8:0 ;; smart_health_failed) smart_health=FAILED ;; smart_reallocated_nonzero) smart_reallocated=1 ;; smart_pending_nonzero) smart_pending=1 ;; smart_uncorrectable_nonzero) smart_uncorrectable=1 ;; ntfs_dirty_or_hibernated) ntfs_probe=fail ;; esac cat <"$argv_log"; } _k3slrv_collect_device_evidence device_ready >/dev/null [[ "$(<"$argv_log")" == '/usr/bin/ntfs-3g.probe --readwrite /dev/fixture-recovery-partition' ]] ) probe_review1_blkid_argv() ( local argv_log="${fixture_root}/review1-blkid.argv" # shellcheck source=/dev/null source "$VALIDATOR_PATH" _k3slrv_one_line_root() { printf '%s\n' "$*" >"$argv_log" printf '%s\n' fixture } _k3slrv_blkid UUID /dev/fixture-recovery-partition >/dev/null [[ "$(<"$argv_log")" == '/usr/sbin/blkid --output value --match-tag UUID -- /dev/fixture-recovery-partition' ]] ) probe_review1_ntfs3_effective_masks() ( local device_raw state_raw # shellcheck source=/dev/null source "$VALIDATOR_PATH" device_raw="$(validator_device_evidence open_exact_lineage)" state_raw="$(validator_state_evidence open_exact_lineage open)" state_raw="${state_raw/umask=077/dmask=0077,fmask=0077}" _k3slrv_validate_state open "$state_raw" "$device_raw" ) probe_review1_initial_guard() ( # shellcheck source=/dev/null source "$VALIDATOR_PATH" declare -F _k3slrv_initial_guard >/dev/null || return 1 _k3slrv_initial_guard 1000 hB || return 1 ! _k3slrv_initial_guard 0 hB ) probe_review1_bash_x_rejected() ( local stdout_file="${fixture_root}/review1-xtrace.stdout" local stderr_file="${fixture_root}/review1-xtrace.stderr" rc set +e /usr/bin/bash -x "$VALIDATOR_PATH" --help >"$stdout_file" 2>"$stderr_file" rc=$? set -e [[ "$rc" -eq 1 && ! -s "$stdout_file" ]] || return 1 [[ "$(<"$stderr_file")" == *'Recovery validation refused'* ]] || return 1 [[ "$(<"$stderr_file")" != *'K3SLR_RECOVERY_FS_UUID'* ]] ) probe_review1_manifest_symlink_escape() ( local root="${fixture_root}/review1-manifest-symlink" bundle outside hash root="$(mktemp -d "${root}.XXXXXX")" bundle="${root}/bundle" outside="${root}/outside" mkdir -p "$bundle/data" "$outside/parent" chmod 0700 "$bundle" "$bundle/data" "$outside" "$outside/parent" printf '%s\n' payload >"$bundle/data/payload" printf '%s\n' escaped >"$outside/leaf" printf '%s\n' escaped-parent >"$outside/parent/payload" hash="$(/usr/bin/sha256sum "$bundle/data/payload")"; hash="${hash%% *}" printf '%s %s\n' "$hash" './data/payload' >"$bundle/verification.manifest" chmod 0600 "$bundle/verification.manifest" "$bundle/data/payload" "$outside/leaf" "$outside/parent/payload" # shellcheck source=/dev/null source "$VALIDATOR_PATH" declare -F _k3slrv_verify_manifest_targets >/dev/null || return 1 _k3slrv_root() { "$@"; } _k3slrv_verify_manifest_targets "$bundle" "$bundle/verification.manifest" || return 1 ln -s "$outside/leaf" "$bundle/leaf-link" hash="$(/usr/bin/sha256sum "$outside/leaf")"; hash="${hash%% *}" printf '%s %s\n' "$hash" './leaf-link' >"$bundle/verification.manifest" ! _k3slrv_verify_manifest_targets "$bundle" "$bundle/verification.manifest" || return 1 ln -s "$outside/parent" "$bundle/parent-link" hash="$(/usr/bin/sha256sum "$outside/parent/payload")"; hash="${hash%% *}" printf '%s %s\n' "$hash" './parent-link/payload' >"$bundle/verification.manifest" ! _k3slrv_verify_manifest_targets "$bundle" "$bundle/verification.manifest" ) probe_review1_partition_mounted_elsewhere() ( # shellcheck source=/dev/null source "$VALIDATOR_PATH" _k3slrv_one_line() { case "${*: -1}" in "$K3SLR_RECOVERY_PARTITION_BY_ID") printf '%s\n' /dev/fixture-recovery-partition ;; "$K3SLR_RECOVERY_DISK_BY_ID") printf '%s\n' /dev/fixture-recovery-disk ;; "$K3SLR_K3S_PARTITION_BY_ID") printf '%s\n' /dev/fixture-k3s-partition ;; "$K3SLR_K3S_DISK_BY_ID") printf '%s\n' /dev/fixture-k3s-disk ;; *) return 1 ;; esac } _k3slrv_lsblk() { printf '%s\n' fixture; } _k3slrv_blkid() { printf '%s\n' fixture; } _k3slrv_smart_evidence() { printf '%s\n' smart_health=PASSED smart_reallocated=0 smart_pending=0 smart_uncorrectable=0 } _k3slrv_findmnt_optional() { :; } _k3slrv_findmnt_source_optional() { printf '%s\n' /mnt/fixture-unapproved; } _k3slrv_root() { :; } ! _k3slrv_collect_device_evidence device_ready >/dev/null ) probe_review1_open_lineage_snapshot() ( local good mutation key current line candidate device_raw state_raw # shellcheck source=/dev/null source "$VALIDATOR_PATH" declare -F _k3slrv_validate_open_snapshot >/dev/null || return 1 good=$'container_chain_pinned=1\ncontainer_inode=4242\ncontainer_device=8:3\nloop_back_inode=4242\nloop_back_major_minor=8:3\nouter_major_minor=8:3\nloop_major_minor=7:0\nmapping_loop_major_minor=7:0\nmapping_major_minor=253:0\ninner_major_minor=253:0\nloop_offset=0\nloop_sizelimit=0\nsnapshot_stable=1' _k3slrv_validate_open_snapshot "$good" || { printf 'review1 snapshot good failed\n' >&2; return 1; } for mutation in \ 'container_chain_pinned=0' \ 'container_device=8:9' \ 'loop_back_inode=9999' \ 'loop_back_major_minor=8:9' \ 'mapping_loop_major_minor=7:9' \ 'mapping_major_minor=253:9' \ 'loop_offset=1' \ 'loop_sizelimit=1' \ 'snapshot_stable=0'; do key="${mutation%%=*}" current='' while IFS= read -r line; do [[ "$line" == "$key="* ]] && current="$line"; done <<<"$good" [[ -n "$current" ]] || return 1 candidate="${good/"$current"/"$mutation"}" ! _k3slrv_validate_open_snapshot "$candidate" || { printf 'review1 snapshot mutation accepted: %s\n' "$mutation" >&2; return 1; } done device_raw="$(validator_device_evidence open_exact_lineage)" state_raw="$(validator_state_evidence open_exact_lineage open)" _k3slrv_validate_state open "$state_raw" "$device_raw" || { printf 'review1 state snapshot good failed\n' >&2; return 1; } ! _k3slrv_validate_state open "${state_raw/snapshot_stable=1/snapshot_stable=0}" "$device_raw" || { printf 'review1 state snapshot drift accepted\n' >&2 return 1 } ) probe_review1_predefined_parser_cannot_bypass() ( local marker="${fixture_root}/review1-parser-attacked" stdout_file="${fixture_root}/review1-parser.stdout" local stderr_file="${fixture_root}/review1-parser.stderr" rc export -f validator_device_evidence validator_state_evidence set +e /usr/bin/bash -c ' source "$1" _k3slr_load_contract() { printf attacked >"$2"; return 1; } _k3slrv_verify_trusted_binaries() { :; } _k3slrv_require_cached_sudo() { :; } _k3slrv_collect_device_evidence() { validator_device_evidence exact_device_ready; } _k3slrv_collect_state_evidence() { validator_state_evidence closed_no_residue device_ready; } k3slr_local_recovery_main --expect-device-ready ' review1-parser "$VALIDATOR_PATH" "$marker" >"$stdout_file" 2>"$stderr_file" rc=$? set -e [[ "$rc" -eq 0 && ! -e "$marker" && ! -s "$stderr_file" ]] || return 1 [[ "$(<"$stdout_file")" == *'Recovery state: device_ready'* ]] ) probe_review2_exported_source_cannot_intercept() ( local marker="${fixture_root}/review2-source-attacked" local stdout_file="${fixture_root}/review2-source.stdout" local stderr_file="${fixture_root}/review2-source.stderr" rc export -f validator_device_evidence validator_state_evidence source() { printf '%s' attacked >"$K3SLR_SOURCE_ATTACK_MARKER" return 1 } export -f source set +e K3SLR_SOURCE_ATTACK_MARKER="$marker" /usr/bin/bash -c ' builtin source -- "$1" _k3slrv_verify_trusted_binaries() { :; } _k3slrv_require_cached_sudo() { :; } _k3slrv_collect_device_evidence() { validator_device_evidence exact_device_ready; } _k3slrv_collect_state_evidence() { validator_state_evidence closed_no_residue device_ready; } k3slr_local_recovery_main --expect-device-ready ' review2-source "$VALIDATOR_PATH" >"$stdout_file" 2>"$stderr_file" rc=$? set -e [[ "$rc" -eq 0 && ! -e "$marker" && ! -s "$stderr_file" ]] || return 1 [[ "$(<"$stdout_file")" == $'Recovery device: match\nRecovery state: device_ready\nLineage: match\nLatest bundle: not_checked' ]] ) validator_command_fake() { local scope="$1" command="$2" argument field='' device='' tag='' format='' target='' mapping value occurrence local stat_mode=600 stat_size=34359738368 stat_blocks=67108864 stat_device=8:3 local smart_health=PASSED smart_reallocated=0 smart_pending=0 smart_uncorrectable=0 shift 2 { printf '%s\t%s' "$scope" "$command" for argument in "$@"; do printf '\t%s' "$argument"; done printf '\n' } >>"$K3SLR_FAKE_LOG" case "$command" in /usr/bin/test) if [[ "${1-}" == -L ]]; then return 1; fi return 0 ;; /usr/bin/stat) while (( $# > 0 )); do case "$1" in --format=*) format="${1#--format=}"; shift ;; --) shift; target="$1"; shift ;; *) target="$1"; shift ;; esac done case "$format|$target" in '%u|%a|%F|'*) printf '%s\n' '0|755|regular file' ;; '%d:%i|%F|/mnt/k3s-recovery-ssd') printf '%s\n' '2051:100|directory' ;; '%d:%i|%F|/mnt/k3s-recovery-ssd/HyeonworksRecovery') printf '%s\n' '2051:101|directory' ;; '%d:%i|%F|/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers') printf '%s\n' '2051:102|directory' ;; '%d:%i|%F|/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks') printf '%s\n' '2051:4242|regular file' ;; '%F|%u|%g|%a|%s|%b|%i|/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks'|\ '%F|%u|%g|%a|%s|%b|%i|%Hd:%Ld|/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks') occurrence="$(/usr/bin/grep -Fc -- $'\t/usr/bin/stat\t--format=%F|%u|%g|%a|%s|%b|%i' "$K3SLR_FAKE_LOG")" if (( occurrence >= 2 )); then case "$K3SLR_FAKE_CASE" in open_drift_container_mode) stat_mode=640 ;; open_drift_container_size) stat_size=34359734272 ;; open_drift_container_allocated) stat_blocks=67108856 ;; esac fi if [[ "$format" == *'%Hd:%Ld' ]]; then printf 'regular file|1000|1000|%s|%s|%s|4242|%s\n' \ "$stat_mode" "$stat_size" "$stat_blocks" "$stat_device" else printf 'regular file|1000|1000|%s|%s|%s|4242\n' \ "$stat_mode" "$stat_size" "$stat_blocks" fi ;; '%u|%g|%a|%F|/srv/recovery/k3s') occurrence="$(/usr/bin/grep -Fc -- $'\t/usr/bin/stat\t--format=%u|%g|%a|%F\t--\t/srv/recovery/k3s' "$K3SLR_FAKE_LOG")" if [[ "$K3SLR_FAKE_CASE" == open_drift_inner_root && "$occurrence" -ge 2 ]]; then printf '%s\n' '0|0|750|directory' else printf '%s\n' '0|0|700|directory' fi ;; *) return 1 ;; esac ;; /usr/bin/sudo) [[ "$*" == '--non-interactive --validate' ]] ;; /usr/bin/readlink) device="${*: -1}" case "$device" in /dev/disk/by-id/wwn-0x500a0751e6aa6254-part3) printf '%s\n' /dev/fixture-recovery-partition ;; /dev/disk/by-id/wwn-0x500a0751e6aa6254) printf '%s\n' /dev/fixture-recovery-disk ;; /dev/disk/by-id/wwn-0x500a07512df28bd1-part1) printf '%s\n' /dev/fixture-k3s-partition ;; /dev/disk/by-id/wwn-0x500a07512df28bd1) printf '%s\n' /dev/fixture-k3s-disk ;; *) printf '%s\n' "$device" ;; esac ;; /usr/bin/lsblk) while (( $# > 0 )); do if [[ "$1" == --output ]]; then field="$2"; shift 2; else device="$1"; shift; fi done case "$field|$device" in 'PKNAME|/dev/fixture-recovery-partition') if [[ "$K3SLR_FAKE_CASE" == wrong_by_id_target ]]; then printf '%s\n' /dev/fixture-wrong-disk; else printf '%s\n' /dev/fixture-recovery-disk; fi ;; 'PKNAME|/dev/fixture-k3s-partition') printf '%s\n' /dev/fixture-k3s-disk ;; 'MODEL|/dev/fixture-recovery-disk') if [[ "$K3SLR_FAKE_CASE" == wrong_model ]]; then printf '%s\n' WRONGMODEL; else printf '%s\n' CT1000MX500SSD1; fi ;; 'SERIAL|/dev/fixture-recovery-disk') if [[ "$K3SLR_FAKE_CASE" == wrong_serial ]]; then printf '%s\n' WRONGSERIAL; else printf '%s\n' 2306E6AA6254; fi ;; 'WWN|/dev/fixture-recovery-disk') if [[ "$K3SLR_FAKE_CASE" == wrong_wwn ]]; then printf '%s\n' 0xaaaaaaaaaaaaaaaa; else printf '%s\n' 0x500a0751e6aa6254; fi ;; 'MAJ:MIN|/dev/fixture-recovery-disk') printf '%s\n' 8:0 ;; 'MODEL|/dev/fixture-k3s-disk') printf '%s\n' CT250MX500SSD1 ;; 'SERIAL|/dev/fixture-k3s-disk') printf '%s\n' 21132DF28BD1 ;; 'WWN|/dev/fixture-k3s-disk') printf '%s\n' 0x500a07512df28bd1 ;; 'MAJ:MIN|/dev/fixture-k3s-disk') if [[ "$K3SLR_FAKE_CASE" == same_parent_disk ]]; then printf '%s\n' 8:0; else printf '%s\n' 8:32; fi ;; 'MAJ:MIN|/dev/fixture-loop') printf '%s\n' 7:0 ;; 'MAJ:MIN|/dev/fixture-other-loop') printf '%s\n' 7:9 ;; 'MAJ:MIN|/dev/mapper/k3s-recovery') printf '%s\n' 253:0 ;; *) return 1 ;; esac ;; /usr/sbin/blkid) while (( $# > 0 )); do if [[ "$1" == --match-tag ]]; then tag="$2"; shift 2; else device="$1"; shift; fi done case "$tag|$device" in 'UUID|/dev/fixture-recovery-partition') if [[ "$K3SLR_FAKE_CASE" == wrong_fs_uuid ]]; then printf '%s\n' AAAAAAAAAAAAAAAA; else printf '%s\n' 4EA0196C0C5FA27E; fi ;; 'PARTUUID|/dev/fixture-recovery-partition') if [[ "$K3SLR_FAKE_CASE" == wrong_partuuid ]]; then printf '%s\n' aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa; else printf '%s\n' 4670aa9f-9045-4bce-930d-9e84dfec9f38; fi ;; 'TYPE|/dev/fixture-recovery-partition') printf '%s\n' ntfs ;; 'UUID|/dev/fixture-k3s-partition') printf '%s\n' b86086ef-2b3c-4638-abcf-fc7f137dcb97 ;; 'PARTUUID|/dev/fixture-k3s-partition') printf '%s\n' b081b955-d6bc-442c-ac0f-db76560a5245 ;; 'TYPE|/dev/fixture-k3s-partition') printf '%s\n' ext4 ;; 'TYPE|/dev/mapper/k3s-recovery') printf '%s\n' ext4 ;; 'LABEL|/dev/mapper/k3s-recovery') occurrence="$(/usr/bin/grep -Fc -- $'\t/usr/sbin/blkid\t--output\tvalue\t--match-tag\tLABEL\t--\t/dev/mapper/k3s-recovery' "$K3SLR_FAKE_LOG")" if [[ "$K3SLR_FAKE_CASE" == open_wrong_inner_label || ( "$K3SLR_FAKE_CASE" == open_drift_inner_label && "$occurrence" -ge 2 ) ]]; then printf '%s\n' WRONG_LABEL else printf '%s\n' K3S_RECOVERY fi ;; *) return 1 ;; esac ;; /usr/sbin/smartctl) case "$K3SLR_FAKE_CASE" in smart_health_failed) smart_health=FAILED ;; smart_reallocated_nonzero) smart_reallocated=1 ;; smart_pending_nonzero) smart_pending=1 ;; smart_uncorrectable_nonzero) smart_uncorrectable=1 ;; esac printf '%s\n' \ "SMART overall-health self-assessment test result: ${smart_health}" \ " 5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - ${smart_reallocated}" \ "197 Current_Pending_Sector 0x0012 100 100 000 Old_age Always - ${smart_pending}" \ "198 Offline_Uncorrectable 0x0010 100 100 000 Old_age Offline - ${smart_uncorrectable}" ;; /usr/bin/findmnt) while (( $# > 0 )); do case "$1" in --mountpoint|--source) field="$1"; target="$2"; shift 2 ;; *) shift ;; esac done if [[ "$field" == --source ]]; then [[ "$K3SLR_FAKE_CASE" == open_* ]] || return 1 printf '%s\n' /mnt/k3s-recovery-ssd elif [[ "$field" == --mountpoint && "$K3SLR_FAKE_CASE" == open_* ]]; then case "$target" in /mnt/k3s-recovery-ssd) if [[ "$K3SLR_FAKE_CASE" == open_wrong_outer_source ]]; then device=/dev/fixture-other-partition; else device=/dev/fixture-recovery-partition; fi if [[ "$K3SLR_FAKE_CASE" == open_wrong_mount_options ]]; then value='rw,nodev,nosuid,uid=1000,gid=1000,dmask=0077,fmask=0077' else value='rw,nodev,nosuid,noexec,uid=1000,gid=1000,dmask=0077,fmask=0077' fi if [[ "$K3SLR_FAKE_CASE" == open_wrong_container_device ]]; then field=8:9; else field=8:3; fi printf '%s %s %s %s %s\n' "$device" ntfs3 "$value" 41 "$field" ;; /srv/recovery/k3s) printf '%s\n' '/dev/mapper/k3s-recovery ext4 rw,nodev,nosuid,noexec 42 253:0' ;; *) return 1 ;; esac else return 1 fi ;; /usr/bin/ntfs-3g.probe) [[ "$#" -eq 2 && "$1" == --readwrite && "$2" == /dev/fixture-recovery-partition ]] || return 2 [[ "$K3SLR_FAKE_CASE" != ntfs_dirty_or_hibernated ]] ;; /usr/sbin/losetup) if [[ "$K3SLR_FAKE_CASE" == open_* ]]; then if [[ "$K3SLR_FAKE_CASE" == open_wrong_container_device ]]; then field=8:9; else field=8:3; fi printf '/dev/fixture-loop 4242 %s 7:0 0 0\n' "$field" elif [[ "$K3SLR_FAKE_CASE" == unexpected_loop ]]; then printf '%s\n' /dev/fixture-loop fi ;; /usr/sbin/cryptsetup) mapping="${*: -1}" if [[ "$mapping" == k3s-recovery-proof ]]; then return 4; fi if [[ "$K3SLR_FAKE_CASE" == open_* || "$K3SLR_FAKE_CASE" == unexpected_mapping ]]; then if [[ "$K3SLR_FAKE_CASE" == open_plain_mapping ]]; then value=plain; else value=LUKS2; fi if [[ "$K3SLR_FAKE_CASE" == open_wrong_backing_file ]]; then device=/dev/fixture-other-loop; else device=/dev/fixture-loop; fi printf '%s\n' "/dev/mapper/k3s-recovery is active and is in use." " type: ${value}" " device: ${device}" else return 4 fi ;; *) return 1 ;; esac } run_validator_command_fixture() { local fixture="$1" stdout_file="$2" stderr_file="$3" log_file="$4" shift 4 : >"$log_file" export -f validator_command_fake K3SLR_FAKE_CASE="$fixture" K3SLR_FAKE_LOG="$log_file" /usr/bin/bash -c ' source "$1"; shift _k3slrv_exec() { validator_command_fake "$@"; } k3slr_local_recovery_main "$@" ' review1-command "$VALIDATOR_PATH" "$@" >"$stdout_file" 2>"$stderr_file" } probe_review1_command_boundary_device_ready() ( local stdout_file="${fixture_root}/review1-command.stdout" stderr_file="${fixture_root}/review1-command.stderr" local log_file="${fixture_root}/review1-command.log" run_validator_command_fixture exact_device_ready "$stdout_file" "$stderr_file" "$log_file" --expect-device-ready || return 1 [[ ! -s "$stderr_file" && "$(<"$stdout_file")" == *'Recovery state: device_ready'* ]] || return 1 /usr/bin/grep -Fqx $'root\t/usr/bin/ntfs-3g.probe\t--readwrite\t/dev/fixture-recovery-partition' "$log_file" || return 1 /usr/bin/grep -Fqx $'root\t/usr/sbin/blkid\t--output\tvalue\t--match-tag\tUUID\t--\t/dev/fixture-recovery-partition' "$log_file" ) probe_review1_command_boundary_open() ( local stdout_file="${fixture_root}/review1-command-open.stdout" stderr_file="${fixture_root}/review1-command-open.stderr" local log_file="${fixture_root}/review1-command-open.log" run_validator_command_fixture open_exact_lineage "$stdout_file" "$stderr_file" "$log_file" --expect-open || return 1 [[ ! -s "$stderr_file" && "$(<"$stdout_file")" == *'Recovery state: open'* ]] || return 1 /usr/bin/grep -Fqx $'root\t/usr/bin/findmnt\t--noheadings\t--raw\t--output\tSOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN\t--mountpoint\t/mnt/k3s-recovery-ssd' "$log_file" || return 1 /usr/bin/grep -Fqx $'root\t/usr/bin/findmnt\t--noheadings\t--raw\t--output\tTARGET\t--source\t/dev/fixture-recovery-partition' "$log_file" || return 1 /usr/bin/grep -Fqx $'root\t/usr/sbin/losetup\t--list\t--associated\t/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks\t--noheadings\t--raw\t--output\tNAME,BACK-INO,BACK-MAJ:MIN,MAJ:MIN,OFFSET,SIZELIMIT' "$log_file" || return 1 /usr/bin/grep -Fqx $'root\t/usr/sbin/cryptsetup\tstatus\t--\tk3s-recovery' "$log_file" || return 1 /usr/bin/grep -Fqx $'root\t/usr/sbin/cryptsetup\tstatus\t--\tk3s-recovery-proof' "$log_file" ) prepare_review1_latest_bundle() { local case_name="$1" root="$2" inner relative bundle outside identity payload_hash metadata_hash inner="${root}/inner" relative='k3s-secrets-encryption-20260801T000000Z/post' bundle="${inner}/${relative}" outside="${root}/outside-payload" mkdir -p -- "$bundle" printf '%s\n' 'encrypted payload fixture' >"${bundle}/payload.bin" printf '%s\n' \ 'schema=platform-k3s-bundle-v1' \ 'bundle_id=01234567-89ab-4cde-8fab-0123456789ab' \ 'phase=post' \ 'k3s_version=v1.36.2+k3s1' \ 'datastore=sqlite' \ 'created_at_utc=2026-08-01T00:00:00Z' \ 'secret_count=1' >"${bundle}/bundle.env" case "$case_name" in malformed) sed -i 's/^schema=.*/schema=not-the-approved-schema/' "${bundle}/bundle.env" ;; duplicate) printf '%s\n' 'phase=post' >>"${bundle}/bundle.env" ;; valid|symlink_escape|hash_mismatch) ;; *) return 1 ;; esac payload_hash="$(/usr/bin/sha256sum -- "${bundle}/payload.bin")"; payload_hash="${payload_hash%% *}" metadata_hash="$(/usr/bin/sha256sum -- "${bundle}/bundle.env")"; metadata_hash="${metadata_hash%% *}" if [[ "$case_name" == symlink_escape ]]; then printf '%s\n' 'escaped payload fixture' >"$outside" ln -s -- "$outside" "${bundle}/escaped-link" payload_hash="$(/usr/bin/sha256sum -- "$outside")"; payload_hash="${payload_hash%% *}" printf '%s %s\n' "$payload_hash" './escaped-link' >"${bundle}/verification.manifest" else [[ "$case_name" != hash_mismatch ]] || payload_hash='0000000000000000000000000000000000000000000000000000000000000000' printf '%s %s\n%s %s\n' \ "$payload_hash" './payload.bin' "$metadata_hash" './bundle.env' >"${bundle}/verification.manifest" fi chmod 0700 -- "$bundle" chmod 0600 -- "${bundle}/bundle.env" "${bundle}/verification.manifest" "${bundle}/payload.bin" identity="$(/usr/bin/stat --format='%d:%i' -- "$bundle")" printf '%s\n' \ 'schema=k3slr-latest-post-bundle-v1' \ "relative_path=${relative}" \ "directory_identity=${identity}" >"${inner}/.latest-post-bundle.env" chmod 0600 -- "${inner}/.latest-post-bundle.env" } run_review1_latest_bundle_case() ( local case_name="$1" expected_rc="$2" root inner rc root="${fixture_root}/review1-latest-${case_name}" mkdir -p -- "$root" prepare_review1_latest_bundle "$case_name" "$root" || return 1 inner="${root}/inner" # shellcheck source=/dev/null source "$VALIDATOR_PATH" K3SLR_INNER_MOUNT="$inner" _k3slrv_exec() { local scope="$1" command="$2" argument mode_kind shift 2 [[ "$scope" == user || "$scope" == root ]] || return 1 if [[ "$command" == /usr/bin/stat && " $* " == *" --format=%u:%g:%a:%F "* ]]; then mode_kind="$($command "${@/--format=%u:%g:%a:%F/--format=%a:%F}")" || return 1 printf '0:0:%s\n' "$mode_kind" return 0 fi "$command" "$@" } set +e _k3slrv_verify_latest_bundle rc=$? set -e [[ "$rc" -eq "$expected_rc" ]] ) probe_review1_latest_bundle_actual() { assert_succeeds run_review1_latest_bundle_case valid 0 assert_succeeds run_review1_latest_bundle_case malformed 1 assert_succeeds run_review1_latest_bundle_case duplicate 1 assert_succeeds run_review1_latest_bundle_case symlink_escape 1 assert_succeeds run_review1_latest_bundle_case hash_mismatch 1 } run_review2_latest_main_case() ( local fixture_name="$1" expected_rc="$2" bundle_case="$3" local root="${fixture_root}/review2-latest-main-${fixture_name}" inner local stdout_file="${fixture_root}/review2-latest-main-${fixture_name}.stdout" local stderr_file="${fixture_root}/review2-latest-main-${fixture_name}.stderr" rc output mkdir -p -- "$root" prepare_review1_latest_bundle "$bundle_case" "$root" || return 1 inner="${root}/inner" export -f validator_device_evidence validator_state_evidence set +e K3SLR_TEST_INNER_MOUNT="$inner" /usr/bin/bash -c ' builtin source -- "$1" _k3slrv_exec() { local scope="$1" command="$2" mode_kind shift 2 [[ "$scope" == user || "$scope" == root ]] || return 1 if [[ "$command" == /usr/bin/stat && " $* " == *" --format=%u:%g:%a:%F "* ]]; then mode_kind="$($command "${@/--format=%u:%g:%a:%F/--format=%a:%F}")" || return 1 printf "0:0:%s\\n" "$mode_kind" return 0 fi "$command" "$@" } _k3slrv_verify_trusted_binaries() { :; } _k3slrv_require_cached_sudo() { K3SLR_INNER_MOUNT="$K3SLR_TEST_INNER_MOUNT"; } _k3slrv_collect_device_evidence() { validator_device_evidence open_exact_lineage; } _k3slrv_collect_state_evidence() { validator_state_evidence open_exact_lineage open; } k3slr_local_recovery_main --expect-open --check-latest-bundle ' review2-latest-main "$VALIDATOR_PATH" >"$stdout_file" 2>"$stderr_file" rc=$? set -e if [[ "$rc" -ne "$expected_rc" ]]; then printf 'task4 feasibility diagnostic: case=%s expected=%s actual=%s stderr=%s\n' \ "$fixture_case" "$expected_rc" "$rc" "$(<"$stderr_file")" >&2 /usr/bin/tail -n 30 "$TASK4_FEASIBILITY_COMMAND_LOG" >&2 return 1 fi if (( expected_rc == 0 )); then [[ ! -s "$stderr_file" ]] || return 1 output="$(<"$stdout_file")" [[ "$output" == $'Recovery device: match\nRecovery state: open\nLineage: match\nLatest bundle: verified' ]] || return 1 else [[ ! -s "$stdout_file" && "$(<"$stderr_file")" == 'Recovery validation failed' ]] || return 1 fi validator_outputs_are_sanitized "$stdout_file" "$stderr_file" ) probe_review2_latest_main_integration() { assert_succeeds run_review2_latest_main_case latest_manifest_valid 0 valid assert_succeeds run_review2_latest_main_case latest_manifest_invalid 1 hash_mismatch } probe_review1_record_preserving_evidence() ( local raw local -A parsed=() # shellcheck source=/dev/null source "$VALIDATOR_PATH" _k3slrv_exec() { printf 'fixture-value\n\n'; } ! _k3slrv_one_line /usr/bin/readlink -f -- /fixture || return 1 declare -F _k3slrv_capture >/dev/null || return 1 trailing_collector() { printf 'alpha=one\n\n'; } _k3slrv_capture raw trailing_collector || return 1 ! _k3slrv_parse_evidence "$raw" parsed alpha || return 1 ! _k3slrv_parse_evidence $'alpha=one\nalpha=two' parsed alpha || return 1 ! _k3slrv_parse_evidence $'alpha=one\nunknown=two' parsed alpha || return 1 ! _k3slrv_parse_evidence $'alpha=one\r' parsed alpha || return 1 ! _k3slrv_parse_evidence $'alpha=one\ncontinued' parsed alpha ) probe_review1_leak_sentinels() ( local stdout_file="${fixture_root}/review1-leak.stdout" stderr_file="${fixture_root}/review1-leak.stderr" : >"$stdout_file" : >"$stderr_file" validator_outputs_are_sanitized "$stdout_file" "$stderr_file" || return 1 printf '%s\n' '4670aa9f-9045-4bce-930d-9e84dfec9f38' >"$stdout_file" ! validator_outputs_are_sanitized "$stdout_file" "$stderr_file" || return 1 : >"$stdout_file" printf '%s\n' '/srv/recovery/k3s/.latest-post-bundle.env' >"$stderr_file" ! validator_outputs_are_sanitized "$stdout_file" "$stderr_file" ) assert_validator_case exact_device_ready 0 device_ready --expect-device-ready assert_validator_case wrong_by_id_target 1 device_ready --expect-device-ready assert_validator_case wrong_fs_uuid 1 device_ready --expect-device-ready assert_validator_case wrong_partuuid 1 device_ready --expect-device-ready assert_validator_case wrong_model 1 device_ready --expect-device-ready assert_validator_case wrong_serial 1 device_ready --expect-device-ready assert_validator_case wrong_wwn 1 device_ready --expect-device-ready assert_validator_case same_parent_disk 1 device_ready --expect-device-ready assert_validator_case smart_health_failed 1 device_ready --expect-device-ready assert_validator_case smart_reallocated_nonzero 1 device_ready --expect-device-ready assert_validator_case smart_pending_nonzero 1 device_ready --expect-device-ready assert_validator_case smart_uncorrectable_nonzero 1 device_ready --expect-device-ready assert_validator_case ntfs_dirty_or_hibernated 1 device_ready --expect-device-ready assert_validator_case closed_no_residue 0 closed --expect-closed assert_validator_case unexpected_loop 1 closed --expect-closed assert_validator_case unexpected_mapping 1 closed --expect-closed assert_validator_case open_exact_lineage 0 open --expect-open assert_validator_case open_wrong_outer_source 1 open --expect-open assert_validator_case open_plain_mapping 1 open --expect-open assert_validator_case open_wrong_backing_file 1 open --expect-open assert_validator_case open_wrong_inner_label 1 open --expect-open assert_validator_case open_wrong_mount_options 1 open --expect-open assert_validator_case open_wrong_container_device 1 open --expect-open assert_validator_case open_drift_container_mode 1 open --expect-open assert_validator_case open_drift_container_size 1 open --expect-open assert_validator_case open_drift_container_allocated 1 open --expect-open assert_validator_case open_drift_inner_label 1 open --expect-open assert_validator_case open_drift_inner_root 1 open --expect-open assert_validator_case exact_device_ready 2 device_ready assert_validator_case exact_device_ready 2 device_ready --expect-open --expect-closed assert_validator_case open_exact_lineage 2 open --expect-closed --check-latest-bundle if [[ -z "$review_focus" || "$review_focus" == 1 ]]; then assert_succeeds probe_review1_ntfs_probe_argv fi if [[ -z "$review_focus" || "$review_focus" == 2 ]]; then assert_succeeds probe_review1_blkid_argv fi if [[ -z "$review_focus" || "$review_focus" == 3 ]]; then assert_succeeds probe_review1_ntfs3_effective_masks fi if [[ -z "$review_focus" || "$review_focus" == 4 ]]; then assert_succeeds probe_review1_initial_guard fi if [[ -z "$review_focus" || "$review_focus" == 5 ]]; then assert_succeeds probe_review1_bash_x_rejected fi if [[ -z "$review_focus" || "$review_focus" == 6 ]]; then assert_succeeds probe_review1_manifest_symlink_escape fi if [[ -z "$review_focus" || "$review_focus" == 7 ]]; then assert_succeeds probe_review1_partition_mounted_elsewhere fi if [[ -z "$review_focus" || "$review_focus" == 8 ]]; then assert_succeeds probe_review1_open_lineage_snapshot fi if [[ -z "$review_focus" || "$review_focus" == 9 ]]; then assert_succeeds probe_review1_predefined_parser_cannot_bypass fi if [[ -z "$review_focus" || "$review_focus" == r2_9 ]]; then assert_succeeds probe_review2_exported_source_cannot_intercept fi if [[ -z "$review_focus" || "$review_focus" == r2_latest ]]; then assert_succeeds probe_review2_latest_main_integration fi if [[ -z "$review_focus" || "$review_focus" == 10 ]]; then assert_succeeds probe_review1_command_boundary_device_ready assert_succeeds probe_review1_command_boundary_open assert_succeeds probe_review1_latest_bundle_actual fi if [[ -z "$review_focus" || "$review_focus" == m1 ]]; then assert_succeeds probe_review1_record_preserving_evidence fi if [[ -z "$review_focus" || "$review_focus" == m2 ]]; then assert_succeeds probe_review1_leak_sentinels fi task4_pipeline_contract() ( declare -F _k3slr_luks_format_from_keepass >/dev/null || return 1 declare -F _k3slr_luks_open_from_keepass >/dev/null || return 1 declare -F _k3slr_password_pipe_preflight >/dev/null || return 1 declare -F _k3slr_keepass_database_lineage_matches >/dev/null || return 1 ) task4_tty_and_secret_api_contract() ( local stdout_file="${fixture_root}/task4-nontty.stdout" local stderr_file="${fixture_root}/task4-nontty.stderr" declare -F _k3slr_keepass_password_stdout >/dev/null || return 1 ! _k3slr_keepass_password_stdout /nonexistent "$K3SLR_KEEPASS_ENTRY" \ "$stdout_file" 2>"$stderr_file" || return 1 [[ ! -s "$stdout_file" ]] || return 1 declare -F _k3slr_keepass_password >/dev/null && return 1 declare -F _k3slr_read_keepass_password >/dev/null && return 1 if /usr/bin/rg -n \ '(^|[[:space:]])(local|declare|typeset)[^#]*(secret|password|passphrase)|read[^#]*(secret|password|passphrase)|KEEPASS[^[:space:]]*PASSWORD=|LUKS[^[:space:]]*PASSWORD=' \ "$LIBRARY_PATH" "$FEASIBILITY_PATH" >/dev/null 2>&1; then return 1 fi ) task4_feasibility_command_fake() { local command="$1" operation path metadata process_identity='' shift if [[ ( "${TASK4_FEASIBILITY_CASE:-}" == signal_* || "${TASK4_FEASIBILITY_CASE:-}" == anchor_* ) && ! -e "${TASK4_FEASIBILITY_MAIN_PID_FILE:-/nonexistent}" ]]; then printf '%s\n' "$BASHPID" >"$TASK4_FEASIBILITY_MAIN_PID_FILE" fi printf '%s %s\n' "$command" "$*" >>"$TASK4_FEASIBILITY_COMMAND_LOG" case "$command" in /usr/bin/test) if [[ "${1-}" == -t ]]; then return 0; fi path="${*: -1}" if [[ "$path" == /tmp/k3slr-feasibility.* ]]; then /usr/bin/test "$@"; else return 0; fi ;; /usr/bin/stat) path="${*: -1}" if [[ "$path" == /tmp/k3slr-feasibility.* ]]; then metadata="$(/usr/bin/stat "$@")" || return 1 if [[ -e "${TASK4_ATOMIC_MUTATED_MARKER:-/nonexistent}" && "$TASK4_FEASIBILITY_CASE" == atomic_wrong_owner && "$path" == */synthetic.kdbx ]]; then printf '99999|%s\n' "${metadata#*|}" elif [[ -e "${TASK4_ATOMIC_MUTATED_MARKER:-/nonexistent}" && "$TASK4_FEASIBILITY_CASE" == atomic_parent_drift && "$path" == "$TASK4_FEASIBILITY_REQUESTED_DIR" && ! -s "${TASK4_ATOMIC_PARENT_STAT_LOG:-/nonexistent}" ]]; then printf x >"$TASK4_ATOMIC_PARENT_STAT_LOG" printf '%s|99:99\n' "${metadata%|*}" else printf '%s\n' "$metadata" fi else printf '0|755|regular file\n' fi ;; /usr/bin/id|/usr/bin/mawk|/usr/bin/od|/usr/bin/ps|/usr/bin/sleep|/usr/bin/sha256sum|/usr/bin/cmp|/usr/bin/pkill) if [[ -n "${TASK4_FEASIBILITY_ROLE_LOG:-}" && ( "$command" == /usr/bin/mawk || "$command" == /usr/bin/sha256sum ) ]]; then _k3slr_process_identity process_identity "$BASHPID" || return 1 printf '%s|%s\n' "${command##*/}" "$process_identity" >>"$TASK4_FEASIBILITY_ROLE_LOG" fi "$command" "$@" ;; /usr/bin/readlink) printf '%s\n' "${*: -1}" ;; /usr/bin/dpkg-query) case "${*: -1}" in keepassxc) case "$TASK4_FEASIBILITY_CASE" in package_missing_lf) printf 'ii |2.7.6+dfsg.1-1build3' ;; package_double_lf) printf 'ii |2.7.6+dfsg.1-1build3\n\n' ;; *) printf 'ii |2.7.6+dfsg.1-1build3\n' ;; esac ;; cryptsetup-bin) printf 'ii |2:2.7.0-1ubuntu4.2\n' ;; *) return 92 ;; esac ;; /usr/bin/mktemp) /usr/bin/mkdir -- "$TASK4_FEASIBILITY_REQUESTED_DIR" || return 1 /usr/bin/chmod 0700 -- "$TASK4_FEASIBILITY_REQUESTED_DIR" || return 1 printf '%s\n' "$TASK4_FEASIBILITY_REQUESTED_DIR" ;; /usr/bin/rm) if [[ "$TASK4_FEASIBILITY_CASE" == cleanup_failure && "${1-}" == --recursive ]]; then return 9 fi /usr/bin/rm "$@" ;; /usr/bin/sudo) [[ "${1-}" == --non-interactive && "${2-}" == --validate ]] ;; /usr/bin/keepassxc-cli) operation="$1" if [[ -n "${TASK4_FEASIBILITY_ROLE_LOG:-}" ]]; then _k3slr_process_identity process_identity "$BASHPID" || return 1 printf 'keepass|%s\n' "$process_identity" >>"$TASK4_FEASIBILITY_ROLE_LOG" fi if [[ -n "${TASK4_CHILD_CMDLINE_LOG:-}" ]]; then /usr/bin/tr '\0' '\n' <"/proc/${BASHPID}/cmdline" >>"$TASK4_CHILD_CMDLINE_LOG" /usr/bin/tr '\0' '\n' <"/proc/${BASHPID}/environ" >>"$TASK4_CHILD_ENVIRON_LOG" fi if [[ "$TASK4_FEASIBILITY_CASE" == signal_* && "$operation" == db-create ]]; then printf '%s\n' "$BASHPID" >"$TASK4_FEASIBILITY_CHILD_PID_FILE" trap 'printf terminated >"$TASK4_FEASIBILITY_CHILD_TERM_FILE"; exit 143' TERM INT while :; do /usr/bin/sleep 1; done fi if [[ "$TASK4_FEASIBILITY_CASE" == anchor_* && "$operation" == db-create ]]; then printf '%s\n' "$BASHPID" >"$TASK4_FEASIBILITY_CHILD_PID_FILE" trap '' TERM printf ready >"$TASK4_FEASIBILITY_TERM_IGNORE_READY" while :; do /usr/bin/sleep 1; done fi /usr/bin/mawk '{ next } END { exit 0 }' || return 1 case "$operation" in db-create) path="${*: -1}" ( set -o noclobber; printf 'synthetic-database\n' >"$path" ) || return 1 /usr/bin/chmod 0600 -- "$path" ;; add) path="${@: -2:1}" printf 'synthetic-database-after-add\n' >"${path}.atomic" if [[ "$TASK4_FEASIBILITY_CASE" == atomic_wrong_mode ]]; then /usr/bin/chmod 0644 -- "${path}.atomic" else /usr/bin/chmod 0600 -- "${path}.atomic" fi /usr/bin/mv -- "${path}.atomic" "$path" if [[ "$TASK4_FEASIBILITY_CASE" == atomic_symlink ]]; then /usr/bin/unlink "$path" /usr/bin/ln -s -- /dev/null "$path" fi if [[ -n "${TASK4_ATOMIC_MUTATED_MARKER:-}" ]]; then printf x >"$TASK4_ATOMIC_MUTATED_MARKER"; fi ;; show) [[ "$TASK4_FEASIBILITY_CASE" != synthetic_failure ]] || return 7 if (( $# == 6 )) && [[ "${2-}" == --show-protected && "${3-}" == --attributes && "${4-}" == Password && "${5-}" == */synthetic.kdbx && "${6-}" == "$K3SLR_KEEPASS_ENTRY" ]]; then printf '%s\n' 'Zz9Yy8Xx7Ww6Vv5Uu4Tt3Ss2Rr1Qq0Pp8Oo7Nn6M' elif (( $# == 7 )) && [[ "${2-}" == --quiet && "${3-}" == --attributes && "${4-}" == Title && "${5-}" == --show-attachments && "${6-}" == */synthetic.kdbx && "${7-}" == "$K3SLR_KEEPASS_ENTRY" ]]; then if [[ -f "${6}.attachment-state" ]]; then printf 'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n' else printf 'K3s Recovery LUKS\n\nNo attachments present.\n' fi else return 91 fi ;; attachment-import) path="$3" printf 'synthetic-database-after-attachment\n' >"${path}.atomic" /usr/bin/chmod 0600 -- "${path}.atomic" /usr/bin/mv -- "${path}.atomic" "$path" : >"${path}.attachment-state" ;; attachment-export) path="${*: -1}" printf '%s\n' 'k3slr-attachment-roundtrip-v1' >"$path" ;; *) return 91 ;; esac ;; *) return 90 ;; esac } run_task4_feasibility_fixture() ( local fixture_case="$1" expected_rc="$2" requested_dir="$3" local stdout_file="$4" stderr_file="$5" rc=0 # shellcheck source=/dev/null source "$FEASIBILITY_PATH" export TASK4_FEASIBILITY_CASE="$fixture_case" export TASK4_FEASIBILITY_REQUESTED_DIR="$requested_dir" export TASK4_FEASIBILITY_COMMAND_LOG="${fixture_root}/task4-${fixture_case}.commands" : >"$TASK4_FEASIBILITY_COMMAND_LOG" _k3slr_command() { task4_feasibility_command_fake "$@"; } set +e k3slr_local_recovery_feasibility_main --execute "$stdout_file" 2>"$stderr_file" rc=$? set -e if [[ "$rc" -ne "$expected_rc" ]]; then /usr/bin/tail -n 50 "$TASK4_FEASIBILITY_COMMAND_LOG" >&2; return 1; fi if [[ "$fixture_case" == cleanup_failure ]]; then [[ -d "$requested_dir" ]] || return 1 /usr/bin/rm -rf -- "$requested_dir" else if [[ -e "$requested_dir" ]]; then printf 'task4 feasibility diagnostic: fixture remains\n' >&2; return 1; fi fi if /usr/bin/grep -Fq -- 'Zz9Yy8Xx7Ww6Vv5Uu4Tt3Ss2Rr1Qq0Pp8Oo7Nn6M' "$stdout_file" "$stderr_file"; then printf 'task4 feasibility diagnostic: sentinel output leak\n' >&2 return 1 fi if [[ "$fixture_case" == success ]]; then /usr/bin/grep -Fx -- "/usr/bin/keepassxc-cli db-create --quiet --set-password ${requested_dir}/synthetic.kdbx" "$TASK4_FEASIBILITY_COMMAND_LOG" >/dev/null || return 1 /usr/bin/grep -Fx -- "/usr/bin/keepassxc-cli add --quiet --generate --length 40 --lower --upper --numeric --every-group ${requested_dir}/synthetic.kdbx ${K3SLR_KEEPASS_ENTRY}" "$TASK4_FEASIBILITY_COMMAND_LOG" >/dev/null || return 1 /usr/bin/grep -Fx -- "/usr/bin/keepassxc-cli show --show-protected --attributes Password ${requested_dir}/synthetic.kdbx ${K3SLR_KEEPASS_ENTRY}" "$TASK4_FEASIBILITY_COMMAND_LOG" >/dev/null || return 1 [[ "$(/usr/bin/grep -Fxc -- "/usr/bin/keepassxc-cli show --quiet --attributes Title --show-attachments ${requested_dir}/synthetic.kdbx ${K3SLR_KEEPASS_ENTRY}" "$TASK4_FEASIBILITY_COMMAND_LOG")" -eq 2 ]] || return 1 /usr/bin/grep -Fx -- "/usr/bin/keepassxc-cli attachment-import --quiet ${requested_dir}/synthetic.kdbx ${K3SLR_KEEPASS_ENTRY} fixture.bin ${requested_dir}/input.bin" "$TASK4_FEASIBILITY_COMMAND_LOG" >/dev/null || return 1 /usr/bin/grep -E -- "^/usr/bin/keepassxc-cli attachment-export --quiet ${requested_dir}/synthetic\\.kdbx ${K3SLR_KEEPASS_ENTRY} fixture\\.bin /proc/[0-9]+/fd/[0-9]+$" "$TASK4_FEASIBILITY_COMMAND_LOG" >/dev/null || return 1 if [[ "$(( $(/usr/bin/wc -l <"$stdout_file") ))" -ne 1 ]]; then printf 'task4 diagnostic: success stdout line count mismatch\n' >&2 return 1 fi if [[ "$(<"$stdout_file")" != 'Local recovery feasibility: pass' ]]; then printf 'task4 diagnostic: success stdout classification mismatch\n' >&2 return 1 fi if [[ -s "$stderr_file" ]]; then printf 'task4 diagnostic: success stderr is nonempty\n' >&2 return 1 fi elif [[ "$fixture_case" == synthetic_failure || "$fixture_case" == cleanup_failure ]]; then [[ ! -s "$stdout_file" && "$(<"$stderr_file")" == 'Local recovery feasibility failed' ]] || return 1 fi return 0 ) task4_review1_blocking_child_is_reaped() ( local signal_name expected_rc requested_dir stdout_file stderr_file child_pid index rc killer_pid local role pid ppid pgid start_time recorded_roles # shellcheck source=/dev/null source "$FEASIBILITY_PATH" _k3slr_lifecycle_boundary() { local phase="$1" tracked_pid="${2-}" tracked_identity='' if [[ "$phase" == feasibility-pid-published && -n "${TASK4_FEASIBILITY_ROLE_LOG:-}" ]]; then _k3slr_process_identity tracked_identity "$tracked_pid" || return 1 printf 'supervisor|%s\n' "$tracked_identity" >>"$TASK4_FEASIBILITY_ROLE_LOG" fi } for signal_name in TERM INT; do requested_dir="/tmp/k3slr-feasibility.signal${signal_name}${BASHPID}${RANDOM}" stdout_file="${fixture_root}/task4-signal-${signal_name}.stdout" stderr_file="${fixture_root}/task4-signal-${signal_name}.stderr" export TASK4_FEASIBILITY_CASE="signal_${signal_name}" export TASK4_FEASIBILITY_REQUESTED_DIR="$requested_dir" export TASK4_FEASIBILITY_COMMAND_LOG="${fixture_root}/task4-signal-${signal_name}.commands" export TASK4_FEASIBILITY_CHILD_PID_FILE="${fixture_root}/task4-signal-${signal_name}.pid" export TASK4_FEASIBILITY_CHILD_TERM_FILE="${fixture_root}/task4-signal-${signal_name}.terminated" export TASK4_FEASIBILITY_MAIN_PID_FILE="${fixture_root}/task4-signal-${signal_name}.main-pid" export TASK4_FEASIBILITY_ROLE_LOG="${fixture_root}/task4-signal-${signal_name}.roles" : >"$TASK4_FEASIBILITY_COMMAND_LOG" : >"$TASK4_FEASIBILITY_ROLE_LOG" _k3slr_command() { task4_feasibility_command_fake "$@"; } ( for ((index=0; index<100; index++)); do [[ -s "$TASK4_FEASIBILITY_CHILD_PID_FILE" && -s "$TASK4_FEASIBILITY_MAIN_PID_FILE" ]] && break /usr/bin/sleep 0.05 done [[ -s "$TASK4_FEASIBILITY_CHILD_PID_FILE" && -s "$TASK4_FEASIBILITY_MAIN_PID_FILE" ]] || exit 1 kill -"$signal_name" "$(<"$TASK4_FEASIBILITY_MAIN_PID_FILE")" ) & killer_pid=$! set +e k3slr_local_recovery_feasibility_main --execute "$stdout_file" 2>"$stderr_file" rc=$? wait "$killer_pid" set -e child_pid="$(<"$TASK4_FEASIBILITY_CHILD_PID_FILE")" if [[ "$signal_name" == TERM ]]; then expected_rc=143; else expected_rc=130; fi if [[ "$rc" -ne "$expected_rc" || ! -e "$TASK4_FEASIBILITY_CHILD_TERM_FILE" || -e "$requested_dir" ]] || kill -0 "$child_pid" 2>/dev/null; then printf 'task4 signal diagnostic: signal=%s rc=%s expected=%s term=%s fixture=%s live=%s\n' \ "$signal_name" "$rc" "$expected_rc" "$([[ -e "$TASK4_FEASIBILITY_CHILD_TERM_FILE" ]] && printf yes || printf no)" \ "$([[ -e "$requested_dir" ]] && printf yes || printf no)" "$([[ -e /proc/$child_pid ]] && printf yes || printf no)" >&2 return 1 fi recorded_roles=0 while IFS='|' read -r role pid start_time ppid pgid; do [[ "$pid" =~ ^[1-9][0-9]*$ && "$ppid" =~ ^[0-9]+$ && "$pgid" =~ ^[1-9][0-9]*$ && "$start_time" =~ ^[1-9][0-9]*$ ]] || return 1 ! kill -0 "$pid" 2>/dev/null || return 1 recorded_roles=$((recorded_roles + 1)) done <"$TASK4_FEASIBILITY_ROLE_LOG" (( recorded_roles >= 3 )) || return 1 done ) task4_feasibility_cleanup_and_leak_contract() ( local case_name expected_rc output error temporary uid export TASK4_CHILD_CMDLINE_LOG="${fixture_root}/task4-child.cmdline" export TASK4_CHILD_ENVIRON_LOG="${fixture_root}/task4-child.environ" : >"$TASK4_CHILD_CMDLINE_LOG" : >"$TASK4_CHILD_ENVIRON_LOG" [[ -f "$FEASIBILITY_PATH" && ! -L "$FEASIBILITY_PATH" ]] || return 1 for case_name in success synthetic_failure cleanup_failure; do case "$case_name" in success) expected_rc=0 ;; synthetic_failure|cleanup_failure) expected_rc=1 ;; esac temporary="/tmp/k3slr-feasibility.task4${BASHPID}${RANDOM}" output="${fixture_root}/task4-${case_name}.stdout" error="${fixture_root}/task4-${case_name}.stderr" assert_succeeds run_task4_feasibility_fixture "$case_name" "$expected_rc" "$temporary" "$output" "$error" done uid="$(/usr/bin/id -u)" if /usr/bin/find /tmp /run -xdev -user "$uid" \ -name '*Zz9Yy8Xx7Ww6Vv5Uu4Tt3Ss2Rr1Qq0Pp8Oo7Nn6M*' \ -print -quit 2>/dev/null | /usr/bin/grep -q .; then return 1 fi if /usr/bin/find /tmp /run -xdev -user "$uid" -type f -readable \ ! -path "${fixture_root}/*" \ -exec /usr/bin/grep -IlF -- 'Zz9Yy8Xx7Ww6Vv5Uu4Tt3Ss2Rr1Qq0Pp8Oo7Nn6M' '{}' \; \ -print -quit 2>/dev/null | /usr/bin/grep -q .; then return 1 fi ) task4_review1_noclobber_lifecycle() ( local collision fixture_dir target # shellcheck source=/dev/null source "$FEASIBILITY_PATH" export TASK4_FEASIBILITY_CASE=success export TASK4_FEASIBILITY_COMMAND_LOG="${fixture_root}/task4-noclobber.commands" _k3slr_command() { task4_feasibility_command_fake "$@"; } for collision in preexisting symlink; do fixture_dir="/tmp/k3slr-feasibility.noclobber${collision}${BASHPID}${RANDOM}" /usr/bin/mkdir -m 0700 -- "$fixture_dir" : >"$TASK4_FEASIBILITY_COMMAND_LOG" if [[ "$collision" == preexisting ]]; then target="${fixture_dir}/input.bin" printf 'owned\n' >"$target" else target="${fixture_dir}/synthetic.kdbx" /usr/bin/ln -s -- /dev/null "$target" fi K3SLRF_ACTIVE_CHILD_PID='' assert_fails _k3slrf_run_synthetic "$fixture_dir" [[ -e "$target" || -L "$target" ]] || return 1 ! /usr/bin/grep -Fq -- '/usr/bin/keepassxc-cli ' "$TASK4_FEASIBILITY_COMMAND_LOG" || return 1 /usr/bin/rm -rf -- "$fixture_dir" done ) task4_review2_atomic_save_transition() ( local requested_dir="/tmp/k3slr-feasibility.atomic${BASHPID}${RANDOM}" atomic_case local stdout_file="${fixture_root}/task4-review2-atomic.stdout" stderr_file="${fixture_root}/task4-review2-atomic.stderr" export TASK4_ATOMIC_MUTATED_MARKER="${fixture_root}/task4-review2-atomic-mutated" export TASK4_ATOMIC_PARENT_STAT_LOG="${fixture_root}/task4-review2-parent-stat" /usr/bin/unlink "$TASK4_ATOMIC_MUTATED_MARKER" 2>/dev/null || true : >"$TASK4_ATOMIC_PARENT_STAT_LOG" assert_succeeds run_task4_feasibility_fixture success 0 "$requested_dir" "$stdout_file" "$stderr_file" for atomic_case in atomic_symlink atomic_wrong_owner atomic_wrong_mode atomic_parent_drift; do /usr/bin/unlink "$TASK4_ATOMIC_MUTATED_MARKER" 2>/dev/null || true : >"$TASK4_ATOMIC_PARENT_STAT_LOG" requested_dir="/tmp/k3slr-feasibility.atomicfault${BASHPID}${RANDOM}" stdout_file="${fixture_root}/task4-review2-${atomic_case}.stdout" stderr_file="${fixture_root}/task4-review2-${atomic_case}.stderr" assert_succeeds run_task4_feasibility_fixture "$atomic_case" 1 "$requested_dir" "$stdout_file" "$stderr_file" done ) task4_review1_raw_package_record() ( local record_case # shellcheck source=/dev/null source "$FEASIBILITY_PATH" export TASK4_FEASIBILITY_COMMAND_LOG="${fixture_root}/task4-package.commands" : >"$TASK4_FEASIBILITY_COMMAND_LOG" _k3slr_command() { task4_feasibility_command_fake "$@"; } TASK4_FEASIBILITY_CASE=success assert_succeeds _k3slrf_package_version_is_exact keepassxc '2.7.6+dfsg.1-1build3' for record_case in package_missing_lf package_double_lf; do TASK4_FEASIBILITY_CASE="$record_case" assert_fails _k3slrf_package_version_is_exact keepassxc '2.7.6+dfsg.1-1build3' done ) task4_review1_command_fake() { local command="$1" format path role pgid caller_pgid early_signal role_identity='' start_time='' shift printf '%s %s\n' "$command" "$*" >>"$TASK4_REVIEW1_COMMAND_LOG" case "$command" in /usr/bin/test) if [[ "${1-}" == -t && "${TASK4_REVIEW3_SIGNAL_CASE:-}" == 1 && ! -e "${TASK4_REVIEW3_CALLER_PID_FILE:-/nonexistent}" ]]; then printf '%s\n' "$BASHPID" >"$TASK4_REVIEW3_CALLER_PID_FILE" caller_pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID")" || return 1 caller_pgid="${caller_pgid//[[:space:]]/}" printf '%s\n' "$caller_pgid" >"$TASK4_REVIEW3_CALLER_PGID_FILE" fi if [[ "${1-}" == -t && "${TASK4_REVIEW2_REQUIRE_REAL_TTY:-}" == 1 ]]; then printf 'tty-check pid=%s result=%s\n' "$BASHPID" "$([[ -t 0 ]] && printf tty || printf pipe)" >>"$TASK4_REVIEW1_COMMAND_LOG" /usr/bin/test -t 0 return fi if [[ "${1-}" == -t && "${TASK4_REVIEW1_VALIDATION_CASE:-}" == non_tty ]]; then return 1; fi if [[ "${1-}" == '!' && "${2-}" == -L && "${3-}" == "${K3SLR_OUTER_MOUNT}/${K3SLR_ROOT_RELATIVE}/vault" && "${TASK4_REVIEW1_VALIDATION_CASE:-}" == parent_symlink ]]; then return 1; fi return 0 ;; /usr/bin/id) [[ "$*" == '-u' ]] || return 97 printf '1000\n' ;; /usr/bin/readlink) printf '%s\n' "${*: -1}" ;; /usr/bin/stat) format="$1" path="${*: -1}" case "$format" in --format=%u\|%a\|%F) printf '0|755|regular file\n' ;; --format=%F) printf 'block special file\n' ;; --format=%d:%i\|%F\|%u\|%a) case "$path" in "$K3SLR_OUTER_MOUNT") printf '8:10|directory|1000|700\n' ;; "${K3SLR_OUTER_MOUNT}/${K3SLR_ROOT_RELATIVE}") printf '8:11|directory|1000|700\n' ;; "${K3SLR_OUTER_MOUNT}/${K3SLR_ROOT_RELATIVE}/vault") printf '8:12|directory|1000|700\n' ;; "${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}") if [[ "${TASK4_REVIEW1_VALIDATION_CASE:-}" == post_drift ]]; then printf 'x\n' >>"$TASK4_REVIEW1_DB_STAT_LOG" if [[ "$(/usr/bin/wc -l <"$TASK4_REVIEW1_DB_STAT_LOG")" -gt 1 ]]; then printf '8:99|regular file|1000|600\n' else printf '8:13|regular file|1000|600\n' fi else printf '8:13|regular file|1000|600\n' fi ;; *) return 96 ;; esac ;; *) return 95 ;; esac ;; /usr/bin/keepassxc-cli) if [[ -n "${TASK4_REVIEW4_IDENTITY_ROLE_LOG:-}" ]]; then _k3slr_process_identity role_identity "$BASHPID" || return 1 printf 'keepass|%s\n' "$role_identity" >>"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" /usr/bin/sleep 0.05 fi if [[ -n "${TASK4_CHILD_CMDLINE_LOG:-}" ]]; then /usr/bin/tr '\0' '\n' <"/proc/${BASHPID}/cmdline" >>"$TASK4_CHILD_CMDLINE_LOG" /usr/bin/tr '\0' '\n' <"/proc/${BASHPID}/environ" >>"$TASK4_CHILD_ENVIRON_LOG" fi printf '%s\n' "$*" >>"$TASK4_REVIEW1_PRODUCER_LOG" if [[ "${TASK4_REVIEW2_REQUIRE_REAL_TTY:-}" == 1 ]]; then /usr/bin/test -t 0 || return 88 fi if [[ "$TASK4_REVIEW1_PRODUCER_CASE" == blocking_signal ]]; then exec /usr/bin/bash -c ' pgid="$(/usr/bin/ps -o pgid= -p "$$")" || exit 1 pgid="${pgid//[[:space:]]/}" IFS= read -r stat_record <"/proc/$$/stat" || exit 1 stat_tail="${stat_record##*) }" read -r -a stat_fields <<<"$stat_tail" start_time="${stat_fields[19]}" printf "keepass %s %s %s %s\n" "$$" "$PPID" "$pgid" "$start_time" >>"$TASK4_REVIEW3_ROLE_LOG" trap "exit 143" TERM trap "exit 130" INT while :; do :; done ' fi if [[ "$TASK4_REVIEW1_PRODUCER_CASE" == term_ignoring_descendant ]]; then exec /usr/bin/bash -c ' trap "" TERM printf ready >"$TASK4_REVIEW5_TERM_IGNORE_READY" while :; do :; done ' fi case "$TASK4_REVIEW1_PRODUCER_CASE" in partial_failure) printf '%s' partial; return 7 ;; zero_failure) return 8 ;; zero_success) return 0 ;; missing_lf) printf '%s' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' ;; double_lf) printf '%s\n\n' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' ;; invalid_class) printf '%s\n' 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ;; malformed_length) printf '%s\n' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm' ;; oversized_success) /usr/bin/mawk 'BEGIN { for (i=0; i<200000; i++) printf "X"; printf "\n" }' ;; success) printf '%s\n' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' ;; *) return 94 ;; esac ;; /usr/bin/sudo) if [[ "${1-}" == --non-interactive && "${2-}" == --validate ]]; then return 0; fi printf 'dispatch\n' >>"$TASK4_REVIEW1_CONSUMER_LOG" /usr/bin/mawk 'END { exit 0 }' ;; /usr/bin/pkill) /usr/bin/pkill "$@" ;; /usr/bin/ps) if [[ "${TASK4_REVIEW3_EARLY_SIGNAL:-}" =~ ^(INT|TERM)$ && "${1-} ${2-}" == '-o stat=' ]]; then early_signal="$TASK4_REVIEW3_EARLY_SIGNAL" TASK4_REVIEW3_EARLY_SIGNAL='' printf '%s\n' "${*: -1}" >"$TASK4_REVIEW3_SUPERVISOR_PID_FILE" /usr/bin/ps -o pgid= -p "${*: -1}" | /usr/bin/tr -d '[:space:]' >"$TASK4_REVIEW3_SUPERVISOR_PGID_FILE" kill -"$early_signal" "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" fi /usr/bin/ps "$@" ;; /usr/bin/sleep) /usr/bin/sleep "$@" ;; /usr/bin/od|/usr/bin/mawk|/usr/bin/dd) if [[ -n "${TASK4_REVIEW4_IDENTITY_ROLE_LOG:-}" && "$command" != /usr/bin/dd ]]; then role="${command##*/}" _k3slr_process_identity role_identity "$BASHPID" || return 1 printf '%s|%s\n' "$role" "$role_identity" >>"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" fi if [[ "$TASK4_REVIEW1_PRODUCER_CASE" == blocking_signal && "$command" != /usr/bin/dd ]]; then role="${command##*/}" pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID")" || return 1 pgid="${pgid//[[:space:]]/}" _k3slr_process_identity role_identity "$BASHPID" || return 1 IFS='|' read -r _ start_time _ _ <<<"$role_identity" printf '%s %s %s %s %s\n' "$role" "$BASHPID" "$PPID" "$pgid" "$start_time" >>"$TASK4_REVIEW3_ROLE_LOG" exec "$command" "$@" fi "$command" "$@" ;; *) return 93 ;; esac } task4_review1_producer_must_finish_before_consumer() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" export TASK4_REVIEW1_CONSUMER_LOG="${fixture_root}/task4-review1-consumer.log" export TASK4_REVIEW1_PRODUCER_LOG="${fixture_root}/task4-review1-producer.log" export TASK4_REVIEW1_COMMAND_LOG="${fixture_root}/task4-review1-command.log" export TASK4_REVIEW1_DB_STAT_LOG="${fixture_root}/task4-review1-db-stat.log" export TASK4_CHILD_CMDLINE_LOG="${fixture_root}/task4-child.cmdline" export TASK4_CHILD_ENVIRON_LOG="${fixture_root}/task4-child.environ" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_CHILD_CMDLINE_LOG" : >"$TASK4_CHILD_ENVIRON_LOG" _k3slr_command() { task4_review1_command_fake "$@"; } TASK4_REVIEW1_PRODUCER_CASE=partial_failure export TASK4_REVIEW1_PRODUCER_CASE assert_fails _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" [[ ! -s "$TASK4_REVIEW1_CONSUMER_LOG" ]] || return 1 TASK4_REVIEW1_PRODUCER_CASE=zero_failure export TASK4_REVIEW1_PRODUCER_CASE assert_fails _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" [[ ! -s "$TASK4_REVIEW1_CONSUMER_LOG" ]] || return 1 TASK4_REVIEW1_PRODUCER_CASE=success export TASK4_REVIEW1_PRODUCER_CASE if ! _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME"; then printf 'task4 critical diagnostic: producer=%s consumer=%s\n' \ "$(/usr/bin/wc -l <"$TASK4_REVIEW1_PRODUCER_LOG")" \ "$(/usr/bin/wc -l <"$TASK4_REVIEW1_CONSUMER_LOG")" >&2 /usr/bin/tail -n 20 "$TASK4_REVIEW1_COMMAND_LOG" >&2 return 1 fi [[ "$(/usr/bin/wc -l <"$TASK4_REVIEW1_CONSUMER_LOG")" -eq 1 ]] || return 1 [[ "$(/usr/bin/tail -n 1 "$TASK4_REVIEW1_PRODUCER_LOG")" == \ "show --show-protected --attributes Password ${database} ${K3SLR_KEEPASS_ENTRY}" ]] ) task4_review3_cleanup_recorded_roles() { local role pid ppid pgid start_time expected_identity current_identity caller_pgid cleanup_rc=0 [[ -f "$1" ]] || return 0 caller_pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID")" || return 1 caller_pgid="${caller_pgid//[[:space:]]/}" while read -r role pid ppid pgid start_time; do [[ "$pid" =~ ^[1-9][0-9]*$ ]] || continue expected_identity="${pid}|${start_time}|${ppid}|${pgid}" if _k3slr_process_identity current_identity "$pid" 2>/dev/null && [[ "$current_identity" == "$expected_identity" && "$pgid" != "$caller_pgid" ]]; then builtin kill -TERM "$pid" 2>/dev/null || cleanup_rc=1 fi done <"$1" /usr/bin/sleep 0.1 while read -r role pid ppid pgid start_time; do [[ "$pid" =~ ^[1-9][0-9]*$ ]] || continue expected_identity="${pid}|${start_time}|${ppid}|${pgid}" if _k3slr_process_identity current_identity "$pid" 2>/dev/null && [[ "$current_identity" == "$expected_identity" && "$pgid" != "$caller_pgid" ]]; then builtin kill -KILL "$pid" 2>/dev/null || cleanup_rc=1 fi done <"$1" return "$cleanup_rc" } task4_review3_nested_pipeline_signal_cleanup() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local signal_name expected_rc iteration prefix killer_pid rc started_ns ended_ns elapsed_ms local role pid ppid pgid start_time leaked dedicated_pgid caller_pgid export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 export TASK4_REVIEW3_SIGNAL_CASE=1 TASK4_REVIEW1_PRODUCER_CASE=blocking_signal export TASK4_REVIEW1_PRODUCER_CASE _k3slr_command() { task4_review1_command_fake "$@"; } for signal_name in INT TERM; do if [[ "$signal_name" == INT ]]; then expected_rc=130; else expected_rc=143; fi for iteration in 1 2; do prefix="${fixture_root}/task4-review3-${signal_name}-${iteration}" export TASK4_REVIEW1_CONSUMER_LOG="${prefix}.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${prefix}.producer" export TASK4_REVIEW1_COMMAND_LOG="${prefix}.commands" export TASK4_REVIEW1_DB_STAT_LOG="${prefix}.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${prefix}.caller-pid" export TASK4_REVIEW3_CALLER_PGID_FILE="${prefix}.caller-pgid" export TASK4_REVIEW3_ROLE_LOG="${prefix}.roles" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW3_ROLE_LOG" /usr/bin/unlink "$TASK4_REVIEW3_CALLER_PID_FILE" 2>/dev/null || true /usr/bin/unlink "$TASK4_REVIEW3_CALLER_PGID_FILE" 2>/dev/null || true ( for ((probe=0; probe<200; probe++)); do if [[ -s "$TASK4_REVIEW3_CALLER_PID_FILE" && "$(/usr/bin/wc -l <"$TASK4_REVIEW3_ROLE_LOG")" -ge 3 ]]; then break; fi /usr/bin/sleep 0.01 done [[ -s "$TASK4_REVIEW3_CALLER_PID_FILE" && "$(/usr/bin/wc -l <"$TASK4_REVIEW3_ROLE_LOG")" -ge 3 ]] || exit 1 kill -"$signal_name" "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" ) & killer_pid=$! started_ns="$(/usr/bin/date +%s%N)" set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" /dev/null; then leaked=$((leaked + 1)); fi done <"$TASK4_REVIEW3_ROLE_LOG" if [[ "$rc" -ne "$expected_rc" || "$elapsed_ms" -ge 3000 || -s "$TASK4_REVIEW1_CONSUMER_LOG" || "$leaked" -ne 0 || -z "$dedicated_pgid" || "$dedicated_pgid" == "$caller_pgid" ]]; then printf 'task4 review3 signal diagnostic: signal=%s iteration=%s rc=%s elapsed_ms=%s leaked=%s\n' \ "$signal_name" "$iteration" "$rc" "$elapsed_ms" "$leaked" >&2 /usr/bin/sed -n '1,20p' "$TASK4_REVIEW3_ROLE_LOG" >&2 /usr/bin/tail -n 40 "$TASK4_REVIEW1_COMMAND_LOG" >&2 while read -r role pid ppid pgid start_time; do /usr/bin/ps -o pid=,ppid=,pgid=,stat=,args= -p "$pid" >&2 || true done <"$TASK4_REVIEW3_ROLE_LOG" task4_review3_cleanup_recorded_roles "$TASK4_REVIEW3_ROLE_LOG" return 1 fi done done ) task4_review3_launch_window_signal_cleanup() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local signal_name expected_rc prefix rc started_ns ended_ns elapsed_ms supervisor_pid supervisor_pgid local supervisor_identity current_identity caller_pgid cleanup_rc=0 export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 export TASK4_REVIEW3_SIGNAL_CASE=1 TASK4_REVIEW1_PRODUCER_CASE=blocking_signal export TASK4_REVIEW1_PRODUCER_CASE _k3slr_command() { task4_review1_command_fake "$@"; } _k3slr_lifecycle_boundary() { local phase="$1" recorded_pgid='' recorded_identity='' if [[ "$phase" == pid-published && "${TASK4_REVIEW3_EARLY_SIGNAL:-}" =~ ^(INT|TERM)$ ]]; then printf '%s\n' "$producer_pid" >"$TASK4_REVIEW3_SUPERVISOR_PID_FILE" recorded_pgid="$(/usr/bin/ps -o pgid= -p "$producer_pid")" || return 1 recorded_pgid="${recorded_pgid//[[:space:]]/}" printf '%s\n' "$recorded_pgid" >"$TASK4_REVIEW3_SUPERVISOR_PGID_FILE" _k3slr_process_identity recorded_identity "$producer_pid" || return 1 printf '%s\n' "$recorded_identity" >"$TASK4_REVIEW3_SUPERVISOR_IDENTITY_FILE" kill -"$TASK4_REVIEW3_EARLY_SIGNAL" "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" TASK4_REVIEW3_EARLY_SIGNAL='' fi } for signal_name in INT TERM; do if [[ "$signal_name" == INT ]]; then expected_rc=130; else expected_rc=143; fi prefix="${fixture_root}/task4-review3-early-${signal_name}" export TASK4_REVIEW1_CONSUMER_LOG="${prefix}.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${prefix}.producer" export TASK4_REVIEW1_COMMAND_LOG="${prefix}.commands" export TASK4_REVIEW1_DB_STAT_LOG="${prefix}.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${prefix}.caller-pid" export TASK4_REVIEW3_CALLER_PGID_FILE="${prefix}.caller-pgid" export TASK4_REVIEW3_ROLE_LOG="${prefix}.roles" export TASK4_REVIEW3_SUPERVISOR_PID_FILE="${prefix}.supervisor-pid" export TASK4_REVIEW3_SUPERVISOR_PGID_FILE="${prefix}.supervisor-pgid" export TASK4_REVIEW3_SUPERVISOR_IDENTITY_FILE="${prefix}.supervisor-identity" export TASK4_REVIEW3_EARLY_SIGNAL="$signal_name" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW3_ROLE_LOG" /usr/bin/unlink "$TASK4_REVIEW3_CALLER_PID_FILE" 2>/dev/null || true /usr/bin/unlink "$TASK4_REVIEW3_CALLER_PGID_FILE" 2>/dev/null || true started_ns="$(/usr/bin/date +%s%N)" set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" /dev/null; then printf 'task4 review3 early diagnostic: signal=%s rc=%s elapsed_ms=%s supervisor=%s pgid=%s\n' \ "$signal_name" "$rc" "$elapsed_ms" "$supervisor_pid" "$supervisor_pgid" >&2 caller_pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID")" || cleanup_rc=1 caller_pgid="${caller_pgid//[[:space:]]/}" if _k3slr_process_identity current_identity "$supervisor_pid" 2>/dev/null && [[ "$current_identity" == "$supervisor_identity" && "$supervisor_pgid" != "$caller_pgid" ]]; then builtin kill -KILL "$supervisor_pid" 2>/dev/null || cleanup_rc=1 fi (( cleanup_rc == 0 )) || printf 'task4 review3 early cleanup failed\n' >&2 return 1 fi done ) task4_review4_stale_cached_group_boundary() ( local child_a unrelated_pid unrelated_pgid unrelated_identity='' caller_pgid supplied_identity='' local current_identity='' current_pgid='' boundary_rc=0 killed=0 cleanup_rc=0 wait_rc /usr/bin/setsid /usr/bin/sleep 30 & unrelated_pid=$! /usr/bin/sleep 0.05 _k3slr_process_identity unrelated_identity "$unrelated_pid" || return 1 unrelated_pgid="$(/usr/bin/ps -o pgid= -p "$unrelated_pid")" || return 1 unrelated_pgid="${unrelated_pgid//[[:space:]]/}" caller_pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID")" || return 1 caller_pgid="${caller_pgid//[[:space:]]/}" [[ "$unrelated_pgid" =~ ^[1-9][0-9]*$ && "$unrelated_pgid" != "$caller_pgid" ]] || return 1 /usr/bin/sleep 30 & child_a=$! _k3slr_process_identity supplied_identity "$child_a" || return 1 set +e _k3slr_terminate_and_reap "$child_a" "$unrelated_pgid" "$supplied_identity" boundary_rc=$? set -e if ! kill -0 "$unrelated_pid" 2>/dev/null; then killed=1; fi if kill -0 "$unrelated_pid" 2>/dev/null; then _k3slr_process_identity current_identity "$unrelated_pid" 2>/dev/null || current_identity='' current_pgid="$(/usr/bin/ps -o pgid= -p "$unrelated_pid" 2>/dev/null || true)" current_pgid="${current_pgid//[[:space:]]/}" caller_pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID" 2>/dev/null || true)" caller_pgid="${caller_pgid//[[:space:]]/}" if [[ "$current_identity" == "$unrelated_identity" && "$current_pgid" == "$unrelated_pgid" && "$caller_pgid" =~ ^[1-9][0-9]*$ && "$current_pgid" != "$caller_pgid" ]]; then builtin kill -TERM -- "-${unrelated_pgid}" 2>/dev/null || cleanup_rc=1 fi fi if wait "$unrelated_pid" 2>/dev/null; then wait_rc=0; else wait_rc=$?; fi (( wait_rc != 127 )) || cleanup_rc=1 if _k3slr_process_identity current_identity "$child_a" 2>/dev/null && [[ "$current_identity" == "$supplied_identity" ]]; then builtin kill -TERM "$child_a" 2>/dev/null || cleanup_rc=1 fi if wait "$child_a" 2>/dev/null; then wait_rc=0; else wait_rc=$?; fi (( wait_rc != 127 )) || cleanup_rc=1 (( cleanup_rc == 0 )) || return 1 if [[ "$killed" -ne 0 ]]; then printf 'task4 review4 stale group RED: child=%s supplied_group=%s unrelated=killed boundary_rc=%s\n' \ "$child_a" "$unrelated_pgid" "$boundary_rc" >&2 return 1 fi ) task4_review4_direct_fallback_boundaries() ( local mode child_pid child_identity='' child_group='' child_state='' attempt original_group_function for mode in caller_group query_failure; do ( kill -STOP "$BASHPID"; printf 'unexpected child continuation\n' >&2 ) & child_pid=$! child_identity='' for ((attempt=0; attempt<200; attempt++)); do if _k3slr_process_record child_identity child_state "$child_pid" && [[ "$child_state" == T ]]; then break; fi /usr/bin/sleep 0.01 done [[ -n "$child_identity" && "$child_state" == T ]] || return 1 IFS='|' read -r _ _ _ child_group <<<"$child_identity" if [[ "$mode" == query_failure ]]; then original_group_function="$(declare -f _k3slr_process_group_for_pid)" _k3slr_process_group_for_pid() { return 1; } fi _k3slr_terminate_and_reap "$child_pid" "$child_group" "$child_identity" || return 1 if [[ "$mode" == query_failure ]]; then eval "$original_group_function"; fi if kill -0 "$child_pid" 2>/dev/null; then return 1; fi done ) task4_review4_term_ignoring_group_escalates() ( local supervisor_pid supervisor_pgid='' supervisor_identity='' descendant_pid='' state='' attempt local descendant_file="${fixture_root}/task4-review4-term-ignoring-descendant" : >"$descendant_file" set -m ( kill -STOP "$BASHPID" trap '' TERM ( trap '' TERM; while :; do :; done ) & printf '%s\n' "$!" >"$descendant_file" wait ) & supervisor_pid=$! _k3slr_wait_for_stopped_group supervisor_pgid supervisor_identity "$supervisor_pid" || { set +m return 1 } set +m kill -CONT "$supervisor_pid" || return 1 for ((attempt=0; attempt<200; attempt++)); do [[ -s "$descendant_file" ]] && break /usr/bin/sleep 0.01 done descendant_pid="$(<"$descendant_file")" [[ "$descendant_pid" =~ ^[1-9][0-9]*$ ]] || return 1 _k3slr_terminate_and_reap "$supervisor_pid" "$supervisor_pgid" "$supervisor_identity" || return 1 ! kill -0 "$supervisor_pid" 2>/dev/null || return 1 ! kill -0 "$descendant_pid" 2>/dev/null || return 1 ! _k3slr_group_exists "$supervisor_pgid" ) task4_review4_wait_clear_is_atomic() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local rc role pid ppid pgid start_time sentinel_pid sentinel_identity='' current_identity='' local seen_supervisor=0 seen_process_sub=0 seen_keepass=0 seen_od=0 seen_mawk=0 export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 TASK4_REVIEW3_SIGNAL_CASE=1 export TASK4_REVIEW1_PRODUCER_CASE=success export TASK4_REVIEW1_CONSUMER_LOG="${fixture_root}/task4-review4-wait-clear.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${fixture_root}/task4-review4-wait-clear.producer" export TASK4_REVIEW1_COMMAND_LOG="${fixture_root}/task4-review4-wait-clear.commands" export TASK4_REVIEW1_DB_STAT_LOG="${fixture_root}/task4-review4-wait-clear.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${fixture_root}/task4-review4-wait-clear.caller" export TASK4_REVIEW3_CALLER_PGID_FILE="${fixture_root}/task4-review4-wait-clear.caller-pgid" export TASK4_REVIEW4_IDENTITY_ROLE_LOG="${fixture_root}/task4-review4-wait-clear.roles" export TASK4_REVIEW4_NEGATIVE_KILL_LOG="${fixture_root}/task4-review4-wait-clear.negative-kill" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" : >"$TASK4_REVIEW4_NEGATIVE_KILL_LOG" _k3slr_command() { task4_review1_command_fake "$@"; } _k3slr_lifecycle_boundary() { local phase="$1" boundary_pid="${2-}" boundary_identity='' case "$phase" in pid-published) _k3slr_process_identity boundary_identity "$boundary_pid" || return 1 printf 'supervisor|%s\n' "$boundary_identity" >>"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" ;; validator-stage-published) _k3slr_process_identity boundary_identity "$boundary_pid" || return 1 printf 'process_sub|%s\n' "$boundary_identity" >>"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" ;; wait-reaped-before-clear) builtin kill -TERM "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" ;; esac } kill() { local argument for argument in "$@"; do if [[ "$argument" =~ ^-[1-9][0-9]*$ ]]; then printf '%s\n' "$argument" >>"$TASK4_REVIEW4_NEGATIVE_KILL_LOG"; fi done builtin kill "$@" } /usr/bin/setsid /usr/bin/sleep 30 & sentinel_pid=$! /usr/bin/sleep 0.02 _k3slr_process_identity sentinel_identity "$sentinel_pid" || return 1 set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" /dev/null || return 1 done <"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" (( seen_supervisor && seen_process_sub && seen_keepass && seen_od && seen_mawk )) || return 1 _k3slr_process_identity current_identity "$sentinel_pid" || return 1 [[ "$current_identity" == "$sentinel_identity" ]] || return 1 builtin kill -TERM "$sentinel_pid" wait "$sentinel_pid" 2>/dev/null || true ) task4_review4_signal_phase_matrix() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local signal_name expected_rc injection_phase phase rc prefix supervisor_pid='' supervisor_pgid='' local supervisor_identity='' sentinel_pid sentinel_identity='' current_identity='' role pid ppid pgid local sentinel_start_time='' sentinel_parent='' sentinel_pgid='' fixture_cleanup_rc=0 local -a phases=(coproc-launch-before pid-published stop-query-complete cont-before cont-after \ wait-reaped-before-clear pre-consumer) export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 TASK4_REVIEW3_SIGNAL_CASE=1 /usr/bin/setsid /usr/bin/sleep 60 & sentinel_pid=$! /usr/bin/sleep 0.02 _k3slr_process_identity sentinel_identity "$sentinel_pid" || return 1 IFS='|' read -r sentinel_pid sentinel_start_time sentinel_parent sentinel_pgid <<<"$sentinel_identity" _task4_review4_phase_sentinel_cleanup() { if _k3slr_process_identity current_identity "$sentinel_pid" 2>/dev/null && [[ "$current_identity" == "$sentinel_identity" ]]; then _k3slr_signal_pid TERM "$sentinel_pid" "$sentinel_identity" "$sentinel_parent" \ "$sentinel_pgid" 2>/dev/null || return 1 if _k3slr_wait_child "$sentinel_pid"; then :; else [[ "$?" -ne 127 ]] || return 1; fi fi } _task4_review4_phase_exit_cleanup() { local original_status="$1" cleanup_status=0 trap - EXIT if [[ -n "$prefix" ]]; then _task4_review5_phase_fixture_cleanup "$prefix" || cleanup_status=1 fi _task4_review4_phase_sentinel_cleanup || cleanup_status=1 (( cleanup_status == 0 )) || exit 1 exit "$original_status" } trap '_task4_review4_phase_exit_cleanup "$?"' EXIT _k3slr_command() { task4_review1_command_fake "$@"; } _k3slr_lifecycle_boundary() { local boundary_phase="$1" boundary_pid="${2-}" recorded_pgid='' if [[ "$boundary_phase" == pid-published ]]; then printf '%s\n' "$boundary_pid" >"${prefix}.supervisor-pid" _k3slr_process_identity supervisor_identity "$boundary_pid" || return 1 printf '%s\n' "$supervisor_identity" >"${prefix}.supervisor-identity" recorded_pgid="$(/usr/bin/ps -o pgid= -p "$boundary_pid")" || return 1 recorded_pgid="${recorded_pgid//[[:space:]]/}" printf '%s\n' "$recorded_pgid" >"${prefix}.supervisor-pgid" fi if [[ "$boundary_phase" == "$injection_phase" ]]; then printf 'injected\n' >"${prefix}.injected" builtin kill -"$signal_name" "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" fi } for signal_name in INT TERM; do if [[ "$signal_name" == INT ]]; then expected_rc=130; else expected_rc=143; fi for phase in "${phases[@]}"; do injection_phase="$phase" prefix="${fixture_root}/task4-review4-phase-${signal_name}-${phase}" export TASK4_REVIEW1_CONSUMER_LOG="${prefix}.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${prefix}.producer" export TASK4_REVIEW1_COMMAND_LOG="${prefix}.commands" export TASK4_REVIEW1_DB_STAT_LOG="${prefix}.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${prefix}.caller-pid" export TASK4_REVIEW3_CALLER_PGID_FILE="${prefix}.caller-pgid" export TASK4_REVIEW3_ROLE_LOG="${prefix}.roles" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW3_ROLE_LOG" /usr/bin/unlink "${prefix}.injected" "${prefix}.supervisor-pid" \ "${prefix}.supervisor-pgid" "${prefix}.supervisor-identity" 2>/dev/null || true if [[ "$phase" == wait-reaped-before-clear || "$phase" == pre-consumer ]]; then TASK4_REVIEW1_PRODUCER_CASE=success else TASK4_REVIEW1_PRODUCER_CASE=blocking_signal fi export TASK4_REVIEW1_PRODUCER_CASE set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" &2 return 1 fi if [[ "$phase" == coproc-launch-before || "$phase" == pid-published || "$phase" == stop-query-complete || "$phase" == cont-before ]] && [[ -s "$TASK4_REVIEW3_ROLE_LOG" ]]; then printf 'task4 review5 pre-CONT worker dispatch: signal=%s phase=%s\n' \ "$signal_name" "$phase" >&2 return 1 fi if [[ -s "${prefix}.supervisor-pid" ]]; then supervisor_pid="$(<"${prefix}.supervisor-pid")" supervisor_pgid="$(<"${prefix}.supervisor-pgid")" if kill -0 "$supervisor_pid" 2>/dev/null || _k3slr_group_exists "$supervisor_pgid"; then printf 'task4 review4 phase leak: signal=%s phase=%s supervisor=%s pgid=%s\n' \ "$signal_name" "$phase" "$supervisor_pid" "$supervisor_pgid" >&2 return 1 fi fi while read -r role pid ppid pgid start_time; do [[ "$pid" =~ ^[1-9][0-9]*$ ]] || continue ! kill -0 "$pid" 2>/dev/null || return 1 done <"$TASK4_REVIEW3_ROLE_LOG" _k3slr_process_identity current_identity "$sentinel_pid" || return 1 [[ "$current_identity" == "$sentinel_identity" ]] || return 1 done done return 0 ) task4_review5_exact_proc_record_contract() ( local raw_file="${fixture_root}/task4-review5-proc-stat" raw_record='' identity='' state='' live_identity='' local valid='123 (comm with ) and ) delimiter) T 0 456 456 0 -1 4194560 1 2 3 4 5 6 7 8 9 10 11 12 18446744073709551615' printf '%s\n' "$valid" >"$raw_file" _k3slr_read_exact_record raw_record "$raw_file" || return 1 [[ "$raw_record" == "$valid" ]] || return 1 _k3slr_parse_proc_stat_record identity state 123 "$raw_record" || return 1 [[ "$identity" == '123|18446744073709551615|0|456' && "$state" == T ]] || return 1 _k3slr_parse_proc_stat_record identity state 123 \ '123 (lower stopped) t 7 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 999999999999999999999999' || return 1 [[ "$identity" == '123|999999999999999999999999|7|456' && "$state" == t ]] || return 1 _k3slr_parse_proc_stat_record identity state 123 \ '123 (idle) I 7 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' || return 1 _k3slr_parse_proc_stat_record identity state 123 \ '123 (paging) W 7 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' || return 1 _k3slr_parse_proc_stat_record identity state 123 \ '123 (consecutive ))) close) R 8 457 457 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 14' || return 1 [[ "$identity" == '123|14|8|457' && "$state" == R ]] || return 1 _k3slr_parse_proc_stat_record identity state 123 \ '123 (decoy ) R 91 777 777 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 15 still comm) S 7 458 458 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 16' || return 1 [[ "$identity" == '123|16|7|458' && "$state" == S ]] || return 1 _k3slr_parse_proc_stat_record identity state 123 \ '123 (comm ending in )) T 6 459 459 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 17' || return 1 [[ "$identity" == '123|17|6|459' && "$state" == T ]] || return 1 _k3slr_process_identity live_identity "$BASHPID" || return 1 [[ "$live_identity" == "${BASHPID}|"* ]] || return 1 local raw_line=sentinel resolved_record=sentinel queried_identity=sentinel local raw_proc_record=sentinel parsed_state=sentinel collision_identity=sentinel printf '%s\n' "$valid" >"$raw_file" _k3slr_read_exact_record raw_line "$raw_file" || return 1 [[ "$raw_line" == "$valid" ]] || return 1 _k3slr_proc_stat_record resolved_record "$BASHPID" || return 1 [[ "$resolved_record" == "${BASHPID} "* ]] || return 1 _k3slr_process_identity queried_identity "$BASHPID" || return 1 [[ "$queried_identity" == "${BASHPID}|"* ]] || return 1 _k3slr_process_record raw_proc_record parsed_state "$BASHPID" || return 1 [[ "$raw_proc_record" == "${BASHPID}|"* && "$parsed_state" != sentinel ]] || return 1 local identity_destination=sentinel process_state=sentinel collision_state=sentinel collision_identity=sentinel _k3slr_parse_proc_stat_record identity_destination collision_state 123 "$valid" || return 1 [[ "$identity_destination" == '123|18446744073709551615|0|456' && "$collision_state" == T ]] || return 1 _k3slr_parse_proc_stat_record collision_identity process_state 123 "$valid" || return 1 [[ "$collision_identity" == '123|18446744073709551615|0|456' && "$process_state" == T ]] || return 1 local destination_name=sentinel resolved_group=sentinel child_pid=sentinel local identity_destination=sentinel state_destination=sentinel group_destination=sentinel _k3slr_capture_one_line destination_name /usr/bin/bash -c 'printf "capture-matrix\n"' || return 1 [[ "$destination_name" == capture-matrix ]] || return 1 _k3slr_process_group_for_pid resolved_group "$BASHPID" || return 1 [[ "$resolved_group" =~ ^[1-9][0-9]*$ ]] || return 1 _k3slr_proc_stat_record child_pid "$BASHPID" || return 1 [[ "$child_pid" == "${BASHPID} "* ]] || return 1 _k3slr_process_identity destination_name "$BASHPID" || return 1 [[ "$destination_name" == "${BASHPID}|"* ]] || return 1 _k3slr_process_record identity_destination state_destination "$BASHPID" || return 1 [[ "$identity_destination" == "${BASHPID}|"* && "$state_destination" != sentinel ]] || return 1 ! _k3slr_parse_proc_stat_record identity identity 123 "$valid" || return 1 ! _k3slr_process_record identity identity "$BASHPID" || return 1 ! _k3slr_wait_for_stopped_group identity identity "$BASHPID" || return 1 ! _k3slr_parse_proc_stat_record _k3slr_parse_state state 123 "$valid" || return 1 printf '%s' "$valid" >"$raw_file" ! _k3slr_read_exact_record raw_record "$raw_file" || return 1 printf '%s\n\n' "$valid" >"$raw_file" ! _k3slr_read_exact_record raw_record "$raw_file" || return 1 printf '%s\r\n' "$valid" >"$raw_file" ! _k3slr_read_exact_record raw_record "$raw_file" || return 1 local malformed for malformed in \ $'123 (cr\rcomm) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ $'123 (embedded\nrecord) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ $'123\t(tab prefix) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ $'123 (tab delimiter)\tT 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (double-space prefix) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123(no-space prefix) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (double-space delimiter) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (no-space delimiter)T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (decoy ) R 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13 real end) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 14' \ '123 (tail close) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 ) 13' \ '124 (wrong pid) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '0123 (leading pid) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (leading ppid) T 00 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (leading pgid) T 0 0456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (leading start) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 00' \ '123 (zero start) T 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 0' \ '123 (bad state) ? 0 456 456 0 -1 1 1 2 3 4 5 6 7 8 9 10 11 12 13' \ '123 (short) T 0 456'; do identity=identity-sentinel state=state-sentinel ! _k3slr_parse_proc_stat_record identity state 123 "$malformed" || return 1 [[ "$identity" == identity-sentinel && "$state" == state-sentinel ]] || return 1 done ) task4_review5_cleanup_exact_recorded_roles() { local log_file="$1" role pid start_time ppid pgid expected_identity current_identity caller_group _k3slr_process_group_for_pid caller_group "$BASHPID" || return 1 while IFS='|' read -r role pid start_time ppid pgid; do [[ "$pid" =~ ^[1-9][0-9]*$ && "$pgid" =~ ^[1-9][0-9]*$ ]] || continue expected_identity="${pid}|${start_time}|${ppid}|${pgid}" if _k3slr_process_identity current_identity "$pid" 2>/dev/null && [[ "$current_identity" == "$expected_identity" && "$pgid" != "$caller_group" ]]; then builtin kill -KILL "$pid" 2>/dev/null || return 1 fi done <"$log_file" } _task4_review5_phase_fixture_cleanup() { local prefix="$1" supervisor_pid='' supervisor_pgid='' supervisor_identity='' local current_identity='' observed_group='' caller_group='' identity_pid='' start_time='' local parent_pid='' identity_group='' role pid ppid pgid expected_identity signal_name wait_rc local cleanup_rc=0 if [[ -s "${prefix}.supervisor-pid" && -s "${prefix}.supervisor-pgid" && -s "${prefix}.supervisor-identity" ]]; then supervisor_pid="$(<"${prefix}.supervisor-pid")" supervisor_pgid="$(<"${prefix}.supervisor-pgid")" supervisor_identity="$(<"${prefix}.supervisor-identity")" IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$supervisor_identity" if _k3slr_process_identity current_identity "$supervisor_pid" 2>/dev/null && [[ "$current_identity" == "$supervisor_identity" && "$identity_pid" == "$supervisor_pid" && "$identity_group" == "$supervisor_pgid" ]]; then _k3slr_process_group_for_pid observed_group "$supervisor_pid" 2>/dev/null || observed_group='' _k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || caller_group='' if [[ "$observed_group" == "$supervisor_pgid" && "$observed_group" != "$caller_group" ]]; then _k3slr_terminate_and_reap "$supervisor_pid" "$supervisor_pgid" \ "$supervisor_identity" 2>/dev/null || : fi fi if _k3slr_process_identity current_identity "$supervisor_pid" 2>/dev/null && [[ "$current_identity" == "$supervisor_identity" ]]; then _k3slr_process_group_for_pid observed_group "$supervisor_pid" 2>/dev/null || observed_group='' _k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || caller_group='' if [[ "$observed_group" == "$supervisor_pgid" && "$observed_group" != "$caller_group" ]]; then _k3slr_signal_pid KILL "$supervisor_pid" "$supervisor_identity" "$parent_pid" \ "$supervisor_pgid" 2>/dev/null || : if _k3slr_wait_child "$supervisor_pid"; then wait_rc=0; else wait_rc=$?; fi (( wait_rc != 127 )) || cleanup_rc=1 fi fi fi if [[ -f "${prefix}.roles" ]]; then for signal_name in TERM KILL; do while read -r role pid ppid pgid start_time; do [[ "$pid" =~ ^[1-9][0-9]*$ && "$ppid" =~ ^[0-9]+$ && "$pgid" =~ ^[1-9][0-9]*$ && "$start_time" =~ ^[1-9][0-9]*$ ]] || continue expected_identity="${pid}|${start_time}|${ppid}|${pgid}" if _k3slr_process_identity current_identity "$pid" 2>/dev/null && [[ "$current_identity" == "$expected_identity" ]]; then _k3slr_process_group_for_pid observed_group "$pid" 2>/dev/null || observed_group='' _k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || caller_group='' if [[ "$observed_group" == "$pgid" && "$observed_group" != "$caller_group" ]]; then _k3slr_signal_pid "$signal_name" "$pid" "$expected_identity" "$ppid" "$pgid" \ 2>/dev/null || : fi fi done <"${prefix}.roles" [[ "$signal_name" == TERM ]] && /usr/bin/sleep 0.1 done while read -r role pid ppid pgid start_time; do [[ "$pid" =~ ^[1-9][0-9]*$ ]] || continue expected_identity="${pid}|${start_time}|${ppid}|${pgid}" if _k3slr_process_identity current_identity "$pid" 2>/dev/null && [[ "$current_identity" == "$expected_identity" ]]; then cleanup_rc=1 fi done <"${prefix}.roles" fi if [[ -n "$supervisor_pid" ]] && _k3slr_process_identity current_identity "$supervisor_pid" 2>/dev/null && [[ "$current_identity" == "$supervisor_identity" ]]; then cleanup_rc=1 fi return "$cleanup_rc" } task4_review5_phase_fixture_failure_cleanup_contract() ( local prefix="${fixture_root}/task4-review5-phase-fixture-cleanup" local supervisor_pid supervisor_identity='' supervisor_pgid='' current_identity='' /usr/bin/setsid /usr/bin/bash -c 'trap "" TERM; while :; do /usr/bin/sleep 1; done' & supervisor_pid=$! /usr/bin/sleep 0.02 _k3slr_process_identity supervisor_identity "$supervisor_pid" || return 1 _k3slr_process_group_for_pid supervisor_pgid "$supervisor_pid" || return 1 printf '%s\n' "$supervisor_pid" >"${prefix}.supervisor-pid" printf '%s\n' "$supervisor_pgid" >"${prefix}.supervisor-pgid" printf '%s\n' "$supervisor_identity" >"${prefix}.supervisor-identity" : >"${prefix}.roles" _task4_review5_phase_fixture_cleanup "$prefix" || { if _k3slr_process_identity current_identity "$supervisor_pid" 2>/dev/null && [[ "$current_identity" == "$supervisor_identity" ]]; then _k3slr_signal_pid KILL "$supervisor_pid" "$supervisor_identity" "$BASHPID" \ "$supervisor_pgid" 2>/dev/null || return 1 _k3slr_wait_child "$supervisor_pid" || : fi return 1 } ! kill -0 "$supervisor_pid" 2>/dev/null && ! _k3slr_group_exists "$supervisor_pgid" ) task4_review5_default_supervisor_anchor() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local prefix="${fixture_root}/task4-review5-anchor" killer_pid rc role pid ppid pgid start_time local leaked=0 cleanup_rc=0 supervisor_pgid='' export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 TASK4_REVIEW3_SIGNAL_CASE=1 export TASK4_REVIEW1_PRODUCER_CASE=term_ignoring_descendant export TASK4_REVIEW1_CONSUMER_LOG="${prefix}.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${prefix}.producer" export TASK4_REVIEW1_COMMAND_LOG="${prefix}.commands" export TASK4_REVIEW1_DB_STAT_LOG="${prefix}.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${prefix}.caller-pid" export TASK4_REVIEW3_CALLER_PGID_FILE="${prefix}.caller-pgid" export TASK4_REVIEW4_IDENTITY_ROLE_LOG="${prefix}.roles" export TASK4_REVIEW5_TERM_IGNORE_READY="${prefix}.ready" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" _k3slr_command() { task4_review1_command_fake "$@"; } _k3slr_lifecycle_boundary() { local phase="$1" boundary_pid="${2-}" identity='' if [[ "$phase" == pid-published ]]; then _k3slr_process_identity identity "$boundary_pid" || return 1 printf 'supervisor|%s\n' "$identity" >>"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" supervisor_pgid="$(/usr/bin/ps -o pgid= -p "$boundary_pid")" || return 1 supervisor_pgid="${supervisor_pgid//[[:space:]]/}" printf '%s\n' "$supervisor_pgid" >"${prefix}.pgid" fi } ( for ((probe=0; probe<300; probe++)); do if [[ -s "$TASK4_REVIEW3_CALLER_PID_FILE" && -s "$TASK4_REVIEW5_TERM_IGNORE_READY" ]]; then break; fi /usr/bin/sleep 0.01 done [[ -s "$TASK4_REVIEW3_CALLER_PID_FILE" && -s "$TASK4_REVIEW5_TERM_IGNORE_READY" ]] || exit 1 builtin kill -TERM "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" ) & killer_pid=$! set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" /dev/null; then leaked=$((leaked + 1)); fi done <"$TASK4_REVIEW4_IDENTITY_ROLE_LOG" if (( leaked > 0 )); then task4_review5_cleanup_exact_recorded_roles "$TASK4_REVIEW4_IDENTITY_ROLE_LOG" || cleanup_rc=$? fi [[ "$cleanup_rc" -eq 0 ]] || return 1 if [[ "$rc" -ne 143 || "$leaked" -ne 0 || -s "$TASK4_REVIEW1_CONSUMER_LOG" ]] || _k3slr_group_exists "$supervisor_pgid"; then printf 'task4 review5 anchor RED: rc=%s leaked=%s pgid_live=%s\n' \ "$rc" "$leaked" "$(_k3slr_group_exists "$supervisor_pgid" && printf yes || printf no)" >&2 return 1 fi ) task4_review5_feasibility_default_supervisor_anchor() ( local prefix="${fixture_root}/task4-review5-feas-anchor" requested_dir local killer_pid rc supervisor_pgid='' role pid start_time ppid pgid leaked=0 cleanup_rc=0 source "$FEASIBILITY_PATH" requested_dir="/tmp/k3slr-feasibility.anchor${BASHPID}${RANDOM}" export TASK4_FEASIBILITY_CASE=anchor_TERM export TASK4_FEASIBILITY_REQUESTED_DIR="$requested_dir" export TASK4_FEASIBILITY_COMMAND_LOG="${prefix}.commands" export TASK4_FEASIBILITY_MAIN_PID_FILE="${prefix}.main" export TASK4_FEASIBILITY_CHILD_PID_FILE="${prefix}.child" export TASK4_FEASIBILITY_ROLE_LOG="${prefix}.roles" export TASK4_FEASIBILITY_TERM_IGNORE_READY="${prefix}.ready" : >"$TASK4_FEASIBILITY_COMMAND_LOG" : >"$TASK4_FEASIBILITY_ROLE_LOG" _k3slr_command() { task4_feasibility_command_fake "$@"; } _k3slr_lifecycle_boundary() { local phase="$1" tracked_pid="${2-}" tracked_identity='' if [[ "$phase" == feasibility-pid-published ]]; then _k3slr_process_identity tracked_identity "$tracked_pid" || return 1 printf 'supervisor|%s\n' "$tracked_identity" >>"$TASK4_FEASIBILITY_ROLE_LOG" supervisor_pgid="$(/usr/bin/ps -o pgid= -p "$tracked_pid")" || return 1 supervisor_pgid="${supervisor_pgid//[[:space:]]/}" printf '%s\n' "$supervisor_pgid" >"${prefix}.pgid" fi } ( local probe for ((probe=0; probe<300; probe++)); do if [[ -s "$TASK4_FEASIBILITY_MAIN_PID_FILE" && -s "$TASK4_FEASIBILITY_TERM_IGNORE_READY" ]]; then break; fi /usr/bin/sleep 0.01 done [[ -s "$TASK4_FEASIBILITY_MAIN_PID_FILE" && -s "$TASK4_FEASIBILITY_TERM_IGNORE_READY" ]] || exit 1 builtin kill -TERM "$(<"$TASK4_FEASIBILITY_MAIN_PID_FILE")" ) & killer_pid=$! set +e k3slr_local_recovery_feasibility_main --execute /dev/null 2>"${prefix}.stderr" rc=$? wait "$killer_pid" set -e supervisor_pgid="$(<"${prefix}.pgid")" while IFS='|' read -r role pid start_time ppid pgid; do if kill -0 "$pid" 2>/dev/null; then leaked=$((leaked + 1)); fi done <"$TASK4_FEASIBILITY_ROLE_LOG" if (( leaked > 0 )); then task4_review5_cleanup_exact_recorded_roles "$TASK4_FEASIBILITY_ROLE_LOG" || cleanup_rc=$? fi [[ "$cleanup_rc" -eq 0 ]] || return 1 if [[ "$rc" -ne 143 || "$leaked" -ne 0 || -e "$requested_dir" ]] || _k3slr_group_exists "$supervisor_pgid"; then printf 'task4 review5 feasibility anchor RED: rc=%s leaked=%s fixture=%s pgid_live=%s\n' \ "$rc" "$leaked" "$([[ -e "$requested_dir" ]] && printf yes || printf no)" \ "$(_k3slr_group_exists "$supervisor_pgid" && printf yes || printf no)" >&2 return 1 fi ) task4_review5_published_unpinned_cleanup() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local mode prefix rc supervisor_pid supervisor_pgid recorded_identity current_identity caller_pgid local definition leaked cleanup_rc raw='' state='' definition="$(declare -f _k3slr_proc_stat_record)" eval "${definition/_k3slr_proc_stat_record/_task4_review5_original_proc_stat_record}" export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 TASK4_REVIEW3_SIGNAL_CASE=1 export TASK4_REVIEW1_PRODUCER_CASE=blocking_signal _k3slr_command() { task4_review1_command_fake "$@"; } for mode in missing short malformed persistent; do cleanup_rc=0 prefix="${fixture_root}/task4-review5-unpinned-${mode}" export TASK4_REVIEW1_CONSUMER_LOG="${prefix}.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${prefix}.producer" export TASK4_REVIEW1_COMMAND_LOG="${prefix}.commands" export TASK4_REVIEW1_DB_STAT_LOG="${prefix}.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${prefix}.caller-pid" export TASK4_REVIEW3_CALLER_PGID_FILE="${prefix}.caller-pgid" export TASK4_REVIEW3_ROLE_LOG="${prefix}.roles" export TASK4_REVIEW5_UNPINNED_MODE="$mode" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW3_ROLE_LOG" : >"${prefix}.pid-signals" : >"${prefix}.group-signals" : >"${prefix}.waits" TASK4_REVIEW5_UNPINNED_PID='' _k3slr_lifecycle_boundary() { local phase="$1" boundary_pid="${2-}" raw='' pinned='' state='' if [[ "$phase" == pid-published ]]; then _task4_review5_original_proc_stat_record raw "$boundary_pid" || return 1 _k3slr_parse_proc_stat_record pinned state "$boundary_pid" "$raw" || return 1 printf '%s\n' "$boundary_pid" >"${prefix}.pid" printf '%s\n' "$pinned" >"${prefix}.identity" /usr/bin/ps -o pgid= -p "$boundary_pid" | /usr/bin/tr -d '[:space:]' >"${prefix}.pgid" TASK4_REVIEW5_UNPINNED_PID="$boundary_pid" fi } _k3slr_proc_stat_record() { local destination_name="$1" queried_pid="$2" if [[ -n "$TASK4_REVIEW5_UNPINNED_PID" && "$queried_pid" == "$TASK4_REVIEW5_UNPINNED_PID" ]]; then case "$TASK4_REVIEW5_UNPINNED_MODE" in missing|persistent) return 1 ;; short) printf -v "$destination_name" '%s' "${queried_pid} (short) T 1" ;; malformed) printf -v "$destination_name" '%s' "${queried_pid} (bad) ? 01 02" ;; esac return 0 fi _task4_review5_original_proc_stat_record "$destination_name" "$queried_pid" } _k3slr_signal_published_pid() { printf '%s %s\n' "$1" "$2" >>"${prefix}.pid-signals" builtin kill -KILL "$2" } _k3slr_signal_group() { printf '%s %s\n' "$1" "$3" >>"${prefix}.group-signals" return 99 } _k3slr_wait_child() { printf '%s\n' "$1" >>"${prefix}.waits" wait "$1" >/dev/null 2>&1 } set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" /dev/null || _k3slr_group_exists "$supervisor_pgid"; then leaked=1; fi if (( leaked )); then caller_pgid="$(/usr/bin/ps -o pgid= -p "$BASHPID")" caller_pgid="${caller_pgid//[[:space:]]/}" if _task4_review5_original_proc_stat_record raw "$supervisor_pid" 2>/dev/null && _k3slr_parse_proc_stat_record current_identity state "$supervisor_pid" "$raw" && [[ "$current_identity" == "$recorded_identity" && "$supervisor_pgid" != "$caller_pgid" ]]; then builtin kill -KILL "$supervisor_pid" 2>/dev/null || cleanup_rc=$? else cleanup_rc=1 fi fi [[ "${cleanup_rc:-0}" -eq 0 ]] || return 1 if [[ "$rc" -ne 1 || "$leaked" -ne 0 || -s "$TASK4_REVIEW1_CONSUMER_LOG" || -s "$TASK4_REVIEW3_ROLE_LOG" || -s "${prefix}.group-signals" || "$(/usr/bin/wc -l <"${prefix}.pid-signals")" -ne 1 || "$(/usr/bin/wc -l <"${prefix}.waits")" -ne 1 ]]; then printf 'task4 review5 unpinned diagnostic: mode=%s rc=%s leaked=%s pid_signals=%s group_signals=%s waits=%s\n' \ "$mode" "$rc" "$leaked" "$(/usr/bin/wc -l <"${prefix}.pid-signals")" \ "$(/usr/bin/wc -l <"${prefix}.group-signals")" "$(/usr/bin/wc -l <"${prefix}.waits")" >&2 return 1 fi done ) task4_review5_pending_priority() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" local prefix="${fixture_root}/task4-review5-priority-password" rc requested_dir export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 TASK4_REVIEW3_SIGNAL_CASE=1 export TASK4_REVIEW1_PRODUCER_CASE=blocking_signal export TASK4_REVIEW1_CONSUMER_LOG="${prefix}.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${prefix}.producer" export TASK4_REVIEW1_COMMAND_LOG="${prefix}.commands" export TASK4_REVIEW1_DB_STAT_LOG="${prefix}.db-stat" export TASK4_REVIEW3_CALLER_PID_FILE="${prefix}.caller-pid" export TASK4_REVIEW3_CALLER_PGID_FILE="${prefix}.caller-pgid" export TASK4_REVIEW3_ROLE_LOG="${prefix}.roles" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" : >"$TASK4_REVIEW3_ROLE_LOG" _k3slr_command() { task4_review1_command_fake "$@"; } _k3slr_lifecycle_boundary() { if [[ "$1" == coproc-launch-before ]]; then builtin kill -TERM "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" builtin kill -INT "$(<"$TASK4_REVIEW3_CALLER_PID_FILE")" fi } set +e _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" &2 return 1 fi source "$FEASIBILITY_PATH" requested_dir="/tmp/k3slr-feasibility.priority${BASHPID}${RANDOM}" export TASK4_FEASIBILITY_CASE=signal_TERM export TASK4_FEASIBILITY_REQUESTED_DIR="$requested_dir" export TASK4_FEASIBILITY_COMMAND_LOG="${prefix}.feasibility-commands" export TASK4_FEASIBILITY_MAIN_PID_FILE="${prefix}.feasibility-main" : >"$TASK4_FEASIBILITY_COMMAND_LOG" _k3slr_command() { task4_feasibility_command_fake "$@"; } _k3slr_lifecycle_boundary() { if [[ "$1" == feasibility-launch-before ]]; then builtin kill -TERM "$(<"$TASK4_FEASIBILITY_MAIN_PID_FILE")" builtin kill -INT "$(<"$TASK4_FEASIBILITY_MAIN_PID_FILE")" fi } set +e k3slr_local_recovery_feasibility_main --execute /dev/null 2>"${prefix}.feasibility-stderr" rc=$? set -e if [[ "$rc" -ne 143 || -e "$requested_dir" ]]; then printf 'task4 review5 feasibility priority RED: rc=%s fixture=%s\n' "$rc" \ "$([[ -e "$requested_dir" ]] && printf yes || printf no)" >&2 return 1 fi ) task4_review5_feasibility_cleanup_failure_is_not_signal_success() ( local prefix="${fixture_root}/task4-review5-feas-cleanup-failure" requested_dir local rc child_pid definition raw='' fail_pid='' source "$FEASIBILITY_PATH" requested_dir="/tmp/k3slr-feasibility.pendingfail${BASHPID}${RANDOM}" export TASK4_FEASIBILITY_CASE=signal_TERM export TASK4_FEASIBILITY_REQUESTED_DIR="$requested_dir" export TASK4_FEASIBILITY_COMMAND_LOG="${prefix}.commands" export TASK4_FEASIBILITY_MAIN_PID_FILE="${prefix}.main" : >"$TASK4_FEASIBILITY_COMMAND_LOG" : >"${prefix}.signal-count" definition="$(declare -f _k3slr_proc_stat_record)" eval "${definition/_k3slr_proc_stat_record/_task4_review5_feas_original_proc_stat_record}" _k3slr_command() { task4_feasibility_command_fake "$@"; } _k3slr_lifecycle_boundary() { if [[ "$1" == feasibility-pid-published ]]; then fail_pid="$2" printf '%s\n' "$2" >"${prefix}.pid" builtin kill -TERM "$(<"$TASK4_FEASIBILITY_MAIN_PID_FILE")" fi } _k3slr_proc_stat_record() { if [[ -n "$fail_pid" && "$2" == "$fail_pid" ]]; then return 1; fi _task4_review5_feas_original_proc_stat_record "$@" } _k3slr_signal_published_pid() { printf 'call\n' >>"${prefix}.signal-count" if [[ "$(/usr/bin/wc -l <"${prefix}.signal-count")" -eq 1 ]]; then return 1; fi builtin kill -KILL "$2" } set +e k3slr_local_recovery_feasibility_main --execute /dev/null 2>"${prefix}.stderr" rc=$? set -e child_pid="$(<"${prefix}.pid")" if [[ "$rc" -ne 1 || "$(/usr/bin/wc -l <"${prefix}.signal-count")" -ne 2 || -e "$requested_dir" ]] || kill -0 "$child_pid" 2>/dev/null; then printf 'task4 review5 feasibility cleanup diagnostic: rc=%s calls=%s child_live=%s fixture=%s\n' \ "$rc" "$(/usr/bin/wc -l <"${prefix}.signal-count")" \ "$([[ -e /proc/$child_pid ]] && printf yes || printf no)" \ "$([[ -e "$requested_dir" ]] && printf yes || printf no)" >&2 return 1 fi ) task4_review2_tty_and_bounded_validated_pipe() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" producer_case export TASK4_REVIEW1_CONSUMER_LOG="${fixture_root}/task4-review2-consumer.log" export TASK4_REVIEW1_PRODUCER_LOG="${fixture_root}/task4-review2-producer.log" export TASK4_REVIEW1_COMMAND_LOG="${fixture_root}/task4-review2-command.log" export TASK4_REVIEW1_DB_STAT_LOG="${fixture_root}/task4-review2-db-stat.log" export TASK4_REVIEW2_REQUIRE_REAL_TTY=1 : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" _k3slr_command() { task4_review1_command_fake "$@"; } TASK4_REVIEW1_PRODUCER_CASE=success export TASK4_REVIEW1_PRODUCER_CASE if ! _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" &2 /usr/bin/tail -n 30 "$TASK4_REVIEW1_COMMAND_LOG" >&2 return 1 fi [[ "$(/usr/bin/wc -l <"$TASK4_REVIEW1_CONSUMER_LOG")" -eq 1 ]] || return 1 /usr/bin/grep -q 'result=tty' "$TASK4_REVIEW1_COMMAND_LOG" || return 1 : >"$TASK4_REVIEW1_CONSUMER_LOG" for producer_case in zero_success missing_lf double_lf invalid_class malformed_length oversized_success; do TASK4_REVIEW1_PRODUCER_CASE="$producer_case" export TASK4_REVIEW1_PRODUCER_CASE assert_fails _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" "$command_log" : >"$stdout_file" : >"$stderr_file" # shellcheck source=/dev/null source "$LIBRARY_PATH" _k3slr_command() { printf '%s\0' "$@" >>"$command_log" case "$#:$1:${2-}:${3-}:${4-}:${5-}" in '3:/usr/bin/test:-t:0::') return 0 ;; '7:/usr/bin/keepassxc-cli:show:--show-protected:--attributes:Password') [[ "${6-}" == /fixture/recovery.kdbx && "${7-}" == 'K3s Recovery LUKS' ]] || return 97 printf '%s\n' "$secret_sentinel" ;; '11:/usr/bin/sudo:--non-interactive:--:/usr/sbin/cryptsetup:open') [[ "${6-}" == --readonly && "${7-}" == --type && "${8-}" == luks2 && "${9-}" == --key-file=- && "${10-}" == /dev/loop23 && "${11-}" == k3s-recovery-proof ]] || return 97 IFS= read -r _ || return 1 ;; *) return 97 ;; esac } _k3slr_keepass_password_stdout /fixture/recovery.kdbx 'K3s Recovery LUKS' \ >/dev/null 2>"$stderr_file" || rc=$? if (( rc != 0 )); then mapfile -d '' -t task1_red_argv <"$command_log" [[ " ${task1_red_argv[*]} " == *' /usr/bin/keepassxc-cli show --quiet '* && " ${task1_red_argv[*]} " != *' --show-protected '* ]] || fail 'producer RED did not reach the legacy KeePass argv missing --show-protected' fi assert_eq 0 "$rc" 'protected KeePass producer uses the protected Password argv' rc=0 _k3slr_cryptsetup_open_readonly_stdin /dev/loop23 k3s-recovery-proof \ < <(printf '%s\n' "$secret_sentinel") >"$stdout_file" 2>>"$stderr_file" || rc=$? assert_eq 0 "$rc" 'read-only LUKS consumer succeeds' export TASK4_REVIEW1_CONSUMER_LOG="${fixture_root}/task1-readonly-pipe.consumer" export TASK4_REVIEW1_PRODUCER_LOG="${fixture_root}/task1-readonly-pipe.producer" export TASK4_REVIEW1_COMMAND_LOG="${fixture_root}/task1-readonly-pipe.commands" export TASK4_REVIEW1_DB_STAT_LOG="${fixture_root}/task1-readonly-pipe.db-stat" : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" TASK4_REVIEW1_PRODUCER_CASE=success TASK4_REVIEW1_VALIDATION_CASE='' export TASK4_REVIEW1_PRODUCER_CASE TASK4_REVIEW1_VALIDATION_CASE _k3slr_command() { task4_review1_command_fake "$@"; } assert_fails _k3slr_luks_open_readonly_from_keepass /fixture/recovery.kdbx \ 'K3s Recovery LUKS' /dev/loop23 assert_fails _k3slr_luks_open_readonly_from_keepass /fixture/recovery.kdbx \ 'K3s Recovery LUKS' /dev/loop23 k3s-recovery-proof extra rc=0 _k3slr_luks_open_readonly_from_keepass \ "${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" 'K3s Recovery LUKS' \ /dev/loop7 "$K3SLR_PROOF_MAPPING_NAME" "$command_log" : >"$trace_file" # shellcheck source=/dev/null source "$FEASIBILITY_PATH" _k3slrf_require_interactive_stdin() { return 0; } _k3slrf_verify_prerequisites() { return 0; } _k3slrf_require_cached_sudo() { return 0; } _k3slrf_create_fixture_dir() { printf -v "$1" '%s' "$case_root/database" printf -v "$2" '%s' fixture-directory printf -v "$3" '%s' true mkdir -p -- "$case_root/database" } _k3slrf_cleanup_fixture_dir() { [[ "$1" == "$case_root/database" ]]; } _k3slrf_directory_identity() { printf -v "$1" '%s' fixture-directory; } _k3slrf_directory_identity_matches() { [[ "$2" == fixture-directory ]]; } _k3slrf_file_identity() { [[ -f "$2" ]] && printf -v "$1" '%s' "file:$2"; } _k3slrf_file_identity_matches() { [[ -f "$1" && "$2" == "file:$1" ]]; } _k3slrf_run_tracked() { ( "$@" ) & local child=$! wait "$child" } _k3slrf_validate_generated_password_stdin() { local received='' IFS= read -r received [[ "$received" == "$secret_sentinel" ]] } _k3slr_command() { local database='' payload='' printf '%s\0' "$@" >>"$command_log" case "$1:${2-}" in '/usr/bin/sha256sum:') (( $# == 1 )) || return 97 printf 'master-hash\n' >>"$trace_file" while IFS= read -r _; do :; done printf '%064d -\n' 0 ;; '/usr/bin/mawk:'*) (( $# == 2 )) && [[ "${2-}" == '{ print $1 }' ]] || return 97 printf 'master-field\n' >>"$trace_file" while IFS= read -r _; do :; done printf '%s\n' fixture-master ;; '/usr/bin/stat:--format=%s') (( $# == 4 )) && [[ "${3-}" == -- && "${4-}" == "${case_root}/database/keepass-listing."*.stdout ]] || return 97 /usr/bin/stat --format='%s' -- "${4}" ;; '/usr/bin/rm:--') (( $# == 4 )) && [[ "${3-}" == "${case_root}/database/keepass-listing."*.stdout && "${4-}" == "${case_root}/database/keepass-listing."*.stderr ]] || return 97 /usr/bin/rm -- "${3}" "${4}" ;; '/usr/bin/keepassxc-cli:db-create') (( $# == 5 )) && [[ "${3-}" == --quiet && "${4-}" == --set-password && "${5-}" == */synthetic.kdbx ]] || return 97 while IFS= read -r _; do :; done : >"${5}" printf 'db-create\n' >>"$trace_file" ;; '/usr/bin/keepassxc-cli:add') (( $# == 12 )) && [[ "${3-}" == --quiet && "${4-}" == --generate && "${5-}" == --length && "${6-}" == 40 && "${7-}" == --lower && "${8-}" == --upper && "${9-}" == --numeric && "${10-}" == --every-group && "${11-}" == */synthetic.kdbx && "${12-}" == 'K3s Recovery LUKS' ]] || return 97 while IFS= read -r _; do :; done printf '%s\n' entry >"${11}" printf 'entry-add\n' >>"$trace_file" ;; '/usr/bin/keepassxc-cli:show') if (( $# == 7 )); then [[ "${4-}" == --attributes && "${5-}" == Password && "${6-}" == */synthetic.kdbx && "${7-}" == 'K3s Recovery LUKS' ]] || return 97 if [[ "${3-}" == --show-protected ]]; then printf 'password-show-protected\n' >>"$trace_file" while IFS= read -r _; do :; done printf '%s\n' "$secret_sentinel" elif [[ "${3-}" == --quiet ]]; then printf 'password-show-legacy\n' >>"$trace_file" return 97 else return 97 fi elif (( $# == 8 )); then [[ "${3-}" == --quiet && "${4-}" == --attributes && "${5-}" == Title && "${6-}" == --show-attachments && "${7-}" == */synthetic.kdbx && "${8-}" == 'K3s Recovery LUKS' ]] || return 97 while IFS= read -r _; do :; done if [[ -f "${7}.attachment-state" ]]; then printf 'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n' else printf 'K3s Recovery LUKS\n\nNo attachments present.\n' fi else return 97 fi ;; '/usr/bin/keepassxc-cli:attachment-import') (( $# == 7 )) && [[ "${3-}" == --quiet && "${4-}" == */synthetic.kdbx && "${5-}" == 'K3s Recovery LUKS' && "${6-}" == fixture.bin && "${7-}" == */input.bin ]] || return 97 while IFS= read -r _; do :; done printf '%s\n' attached >"${4}.attachment-state" IFS= read -r payload <"${7}" || return 1 printf '%s\n' "$payload" >"${4}.attachment-data" ;; '/usr/bin/keepassxc-cli:attachment-export') (( $# == 7 )) && [[ "${3-}" == --quiet && "${4-}" == */synthetic.kdbx && "${5-}" == 'K3s Recovery LUKS' && "${6-}" == fixture.bin && "${7-}" == /proc/*/fd/* ]] || return 97 while IFS= read -r _; do :; done IFS= read -r payload <"${4}.attachment-data" || return 1 printf '%s\n' "$payload" >"${7}" ;; '/usr/bin/cmp:--silent') (( $# == 5 )) && [[ "${3-}" == -- && "${4-}" == */input.bin && "${5-}" == */output.bin ]] || return 97 ;; *) return 97 ;; esac } k3slr_local_recovery_feasibility_main --execute >"$stdout_file" 2>"$stderr_file" || rc=$? if (( rc != 0 )); then local task1_feas_index=0 task1_feas_legacy_seen=false task1_feas_protected_seen=false mapfile -d '' -t task1_feas_red_argv <"$command_log" for (( task1_feas_index=0; task1_feas_index+6<${#task1_feas_red_argv[@]}; task1_feas_index++ )); do if [[ "${task1_feas_red_argv[task1_feas_index]}" == /usr/bin/keepassxc-cli && "${task1_feas_red_argv[task1_feas_index + 1]}" == show && "${task1_feas_red_argv[task1_feas_index + 2]}" == --show-protected && "${task1_feas_red_argv[task1_feas_index + 3]}" == --attributes && "${task1_feas_red_argv[task1_feas_index + 4]}" == Password && "${task1_feas_red_argv[task1_feas_index + 6]}" == 'K3s Recovery LUKS' ]]; then task1_feas_protected_seen=true break elif [[ "${task1_feas_red_argv[task1_feas_index]}" == /usr/bin/keepassxc-cli && "${task1_feas_red_argv[task1_feas_index + 1]}" == show && "${task1_feas_red_argv[task1_feas_index + 2]}" == --quiet && "${task1_feas_red_argv[task1_feas_index + 3]}" == --attributes && "${task1_feas_red_argv[task1_feas_index + 4]}" == Password && "${task1_feas_red_argv[task1_feas_index + 6]}" == 'K3s Recovery LUKS' ]]; then task1_feas_legacy_seen=true break fi done if "$task1_feas_protected_seen"; then fail "feasibility failed after the protected password stage (stages: $(/usr/bin/tr '\n' ',' <"$trace_file"))" fi "$task1_feas_legacy_seen" || fail "feasibility RED did not reach the exact legacy password show argv (stages: $(/usr/bin/tr '\n' ',' <"$trace_file"))" /usr/bin/grep -Fqx password-show-legacy "$trace_file" || fail 'feasibility RED did not persist the legacy password stage' fi assert_eq 0 "$rc" 'feasibility password check uses the protected Password argv' ! /usr/bin/grep -Fq -- "$secret_sentinel" "$stdout_file" "$stderr_file" "$command_log" || fail 'feasibility fixture leaked the password sentinel' mapfile -d '' -t task1_feas_argv <"$command_log" [[ " ${task1_feas_argv[*]} " == *' --show-protected '* ]] || fail 'feasibility did not request the protected Password field' ) # The raw-NUL record is emitted only by the exact modeled show-attachments # command. It is never placed in a Bash variable or command substitution by # this fixture; feasibility must capture and reject it at its real boundary. task4_review2_feasibility_attachment_listing() ( local case_root="${fixture_root}/task1-feas-listing" local command_log="${case_root}/commands.nul" local trace_file="${case_root}/trace" local stdout_file="${case_root}/stdout" local stderr_file="${case_root}/stderr" local listing_case=raw_nul rc=0 state='' mkdir -p -- "$case_root" : >"$command_log" : >"$trace_file" # shellcheck source=/dev/null source "$FEASIBILITY_PATH" _k3slr_parse_keepass_attachment_listing() { (( $# == 3 )) || return 97 case "$1" in $'K3s Recovery LUKS\n\nNo attachments present.\n') printf -v "$3" '%s' absent printf 'parser:absent\n' >>"$trace_file" ;; $'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n') printf -v "$3" '%s' present printf 'parser:present\n' >>"$trace_file" ;; *) printf 'parser:rejected\n' >>"$trace_file" return 1 ;; esac } _k3slrf_require_interactive_stdin() { return 0; } _k3slrf_verify_prerequisites() { return 0; } _k3slrf_require_cached_sudo() { return 0; } _k3slrf_create_fixture_dir() { local new_dir="${case_root}/database-${RANDOM}" mkdir -p -- "$new_dir" printf -v "$1" '%s' "$new_dir" printf -v "$2" '%s' fixture-directory printf -v "$3" '%s' true } _k3slrf_cleanup_fixture_dir() { [[ "$1" == "${case_root}"/database-* ]]; } _k3slrf_directory_identity() { printf -v "$1" '%s' fixture-directory; } _k3slrf_directory_identity_matches() { [[ "$2" == fixture-directory ]]; } _k3slrf_file_identity() { [[ -f "$2" ]] && printf -v "$1" '%s' "file:$2"; } _k3slrf_file_identity_matches() { [[ -f "$1" && "$2" == "file:$1" ]]; } _k3slrf_run_tracked() { ( "$@" ) & local child=$! wait "$child" } _k3slrf_validate_generated_password_stdin() { IFS= read -r _; } _k3slr_command() { local payload='' printf '%s\0' "$@" >>"$command_log" case "$1:${2-}" in '/usr/bin/sha256sum:') (( $# == 1 )) || return 97 while IFS= read -r _; do :; done printf '%064d -\n' 0 ;; '/usr/bin/mawk:'*) (( $# == 2 )) && [[ "${2-}" == '{ print $1 }' ]] || return 97 while IFS= read -r _; do :; done printf '%s\n' fixture-master ;; '/usr/bin/stat:--format=%s') (( $# == 4 )) && [[ "${3-}" == -- && "${4-}" == "${case_root}/database-"*/keepass-listing.*.stdout ]] || return 97 /usr/bin/stat --format='%s' -- "${4}" ;; '/usr/bin/rm:--') (( $# == 4 )) && [[ "${3-}" == "${case_root}/database-"*/keepass-listing.*.stdout && "${4-}" == "${case_root}/database-"*/keepass-listing.*.stderr ]] || return 97 /usr/bin/rm -- "${3}" "${4}" ;; '/usr/bin/keepassxc-cli:db-create') (( $# == 5 )) && [[ "${3-}" == --quiet && "${4-}" == --set-password && "${5-}" == */synthetic.kdbx ]] || return 97 while IFS= read -r _; do :; done : >"${5}" ;; '/usr/bin/keepassxc-cli:add') (( $# == 12 )) && [[ "${3-}" == --quiet && "${4-}" == --generate && "${5-}" == --length && "${6-}" == 40 && "${7-}" == --lower && "${8-}" == --upper && "${9-}" == --numeric && "${10-}" == --every-group && "${11-}" == */synthetic.kdbx && "${12-}" == 'K3s Recovery LUKS' ]] || return 97 while IFS= read -r _; do :; done : >"${11}.entry" ;; '/usr/bin/keepassxc-cli:show') if (( $# == 7 )); then [[ ( "${3-}" == --quiet || "${3-}" == --show-protected ) && "${4-}" == --attributes && "${5-}" == Password && "${6-}" == */synthetic.kdbx && "${7-}" == 'K3s Recovery LUKS' ]] || return 97 while IFS= read -r _; do :; done printf '%s\n' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' elif (( $# == 8 )); then [[ "${3-}" == --quiet && "${4-}" == --attributes && "${5-}" == Title && "${6-}" == --show-attachments && "${7-}" == */synthetic.kdbx && "${8-}" == 'K3s Recovery LUKS' ]] || return 97 while IFS= read -r _; do :; done case "$listing_case" in raw_nul) printf 'listing:raw-nul\n' >>"$trace_file" printf 'K3s Recovery LUKS\n\nAttachments:\n fixture.bin\0 (30.0 B)\n' ;; absent) if [[ -f "${7}.attachment-state" ]]; then printf 'listing:present\n' >>"$trace_file" printf 'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n' else printf 'listing:absent\n' >>"$trace_file" printf 'K3s Recovery LUKS\n\nNo attachments present.\n' fi ;; present) printf 'listing:present\n' >>"$trace_file" printf 'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n' ;; size_8192) printf 'listing:size-8192\n' >>"$trace_file" printf 'K3s Recovery LUKS\n\n%08172d\n' 0 ;; size_8193) printf 'listing:size-8193\n' >>"$trace_file" printf 'K3s Recovery LUKS\n\n%08173d\n' 0 ;; *) return 97 ;; esac else return 97 fi ;; '/usr/bin/keepassxc-cli:attachment-import') (( $# == 7 )) && [[ "${3-}" == --quiet && "${4-}" == */synthetic.kdbx && "${5-}" == 'K3s Recovery LUKS' && "${6-}" == fixture.bin && "${7-}" == */input.bin ]] || return 97 while IFS= read -r _; do :; done IFS= read -r payload <"${7}" || return 1 printf '%s\n' "$payload" >"${4}.attachment-data" : >"${4}.attachment-state" printf 'import\n' >>"$trace_file" ;; '/usr/bin/keepassxc-cli:attachment-export') (( $# == 7 )) && [[ "${3-}" == --quiet && "${4-}" == */synthetic.kdbx && "${5-}" == 'K3s Recovery LUKS' && "${6-}" == fixture.bin && "${7-}" == /proc/*/fd/* ]] || return 97 while IFS= read -r _; do :; done IFS= read -r payload <"${4}.attachment-data" || return 1 printf '%s\n' "$payload" >"${7}" printf 'export\n' >>"$trace_file" ;; '/usr/bin/cmp:--silent') (( $# == 5 )) && [[ "${3-}" == -- && "${4-}" == */input.bin && "${5-}" == */output.bin ]] || return 97 ;; *) return 97 ;; esac } set +e k3slr_local_recovery_feasibility_main --execute >"$stdout_file" 2>"$stderr_file" rc=$? set -e assert_eq 1 "$(/usr/bin/grep -Fxc 'listing:raw-nul' "$trace_file" || :)" \ 'raw-NUL path dispatches exactly one attachment listing' assert_eq 0 "$(/usr/bin/grep -Fc 'parser:' "$trace_file" || :)" \ 'raw-NUL path reaches no parser' assert_eq 0 "$(/usr/bin/grep -Fxc import "$trace_file" || :)" \ 'raw-NUL path reaches no import' assert_eq 0 "$(/usr/bin/grep -Fxc export "$trace_file" || :)" \ 'raw-NUL path reaches no export' ! /usr/bin/grep -Fiq -- 'null byte' "$stderr_file" || fail 'raw-NUL capture emitted a Bash null-byte warning' (( rc != 0 )) || fail 'raw-NUL attachment listing was accepted before bounded capture rejected it' for listing_case in size_8192 size_8193; do : >"$trace_file" set +e k3slr_local_recovery_feasibility_main --execute >>"$stdout_file" 2>>"$stderr_file" rc=$? set -e (( rc != 0 )) || fail "$listing_case attachment listing was accepted" if [[ "$listing_case" == size_8192 ]]; then assert_eq $'listing:size-8192\nparser:rejected' "$(<"$trace_file")" \ '8192-byte listing reaches parser and is rejected only by grammar' else assert_eq 'listing:size-8193' "$(<"$trace_file")" \ '8193-byte listing is rejected by the capture size gate before parser' fi done : >"$trace_file" listing_case=absent assert_succeeds k3slr_local_recovery_feasibility_main --execute >>"$stdout_file" 2>>"$stderr_file" assert_eq $'listing:absent\nparser:absent\nimport\nlisting:present\nparser:present\nexport' \ "$(<"$trace_file")" 'normal feasibility trace is absent to present with exact parser ordering' ! /usr/bin/grep -Fq -- 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' \ "$stdout_file" "$stderr_file" "$command_log" || fail 'attachment fixture leaked password sentinel' ) task4_review2_attachment_parser_contract() ( local state=unchanged record='' # shellcheck source=/dev/null source "$LIBRARY_PATH" assert_succeeds _k3slr_parse_keepass_attachment_listing \ $'K3s Recovery LUKS\n\nNo attachments present.\n' fixture.bin state assert_eq absent "$state" 'canonical KeePassXC absent record parses' assert_succeeds _k3slr_parse_keepass_attachment_listing \ $'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n' fixture.bin state assert_eq present "$state" 'canonical KeePassXC present record parses' for record in \ $'K3s Recovery LUKS\n\nAttachments:\n archive.bin (1.0 KiB)\n' \ $'K3s Recovery LUKS\n\nAttachments:\n archive.bin (2.5 MiB)\n' \ $'K3s Recovery LUKS\n\nAttachments:\n archive.bin (3.0 GiB)\n'; do state=unchanged assert_succeeds _k3slr_parse_keepass_attachment_listing "$record" archive.bin state assert_eq present "$state" 'parser accepts exact non-fixture attachment and IEC size unit' state=unchanged assert_fails _k3slr_parse_keepass_attachment_listing "$record" fixture.bin state assert_eq unchanged "$state" 'non-target attachment leaves output unchanged' done for record in '' $'K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n fixture.bin (30.0 B)\n' \ $'K3s Recovery LUKS\r\n\r\nNo attachments present.\r\n' \ $'Title: K3s Recovery LUKS\n\nAttachments:\n fixture.bin (30.0 B)\n' \ $'K3s Recovery LUKS\n\nAttachments:\n fixture.bin.old (30.0 B)\n' \ $'K3s Recovery LUKS\n\nNo attachments present.\nextra\n' \ $'K3s Recovery LUKS\n\nNo attachments present.'; do state=unchanged assert_fails _k3slr_parse_keepass_attachment_listing "$record" fixture.bin state assert_eq unchanged "$state" 'malformed attachment record leaves output state unchanged' done printf -v record 'K3s Recovery LUKS\n\n%08172d\n' 0 assert_eq 8192 "${#record}" '8192-byte parser record is final-LF framed' state=unchanged assert_fails _k3slr_parse_keepass_attachment_listing "$record" fixture.bin state assert_eq unchanged "$state" '8192-byte grammar failure leaves output unchanged' printf -v record 'K3s Recovery LUKS\n\n%08173d\n' 0 assert_eq 8193 "${#record}" '8193-byte parser record is final-LF framed' state=unchanged assert_fails _k3slr_parse_keepass_attachment_listing "$record" fixture.bin state assert_eq unchanged "$state" 'oversize parser record leaves output unchanged' assert_fails _k3slr_parse_keepass_attachment_listing \ $'K3s Recovery LUKS\n\nNo attachments present.\n' fixture.bin unsafe-name state=unchanged assert_fails _k3slr_parse_keepass_attachment_listing \ $'K3s Recovery LUKS\n\nNo attachments present.\n' fixture.bin _k3slr_attachment_state assert_eq unchanged "$state" 'reserved production-local output name cannot mutate caller state' record=$'K3s Recovery LUKS\n\nNo attachments present.\n' assert_fails _k3slr_parse_keepass_attachment_listing \ "$record" fixture.bin _k3slr_attachment_record assert_eq $'K3s Recovery LUKS\n\nNo attachments present.\n' "$record" \ 'reserved record-local output name leaves the caller record unchanged' ) task4_review1_two_sentinel_leak_contract() ( local first='Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' local second='Zz9Yy8Xx7Ww6Vv5Uu4Tt3Ss2Rr1Qq0Pp8Oo7Nn6M' local value uid sink [[ "$first" != "$second" && "${#first}" -eq 40 && "${#second}" -eq 40 ]] || return 1 for value in "$first" "$second"; do [[ "$value" =~ [a-z] && "$value" =~ [A-Z] && "$value" =~ [0-9] ]] || return 1 for sink in "${fixture_root}/task4-child.cmdline" "${fixture_root}/task4-child.environ" \ "${fixture_root}"/task4-*.stdout "${fixture_root}"/task4-*.stderr; do [[ -e "$sink" ]] || continue ! /usr/bin/grep -Fq -- "$value" "$sink" || return 1 done uid="$(/usr/bin/id -u)" ! /usr/bin/find /tmp /run -xdev -user "$uid" -name "*${value}*" -print -quit 2>/dev/null | /usr/bin/grep -q . || return 1 ! /usr/bin/find /tmp /run -xdev -user "$uid" -type f -readable \ ! -path "${fixture_root}/*" -exec /usr/bin/grep -IlF -- "$value" '{}' + \ 2>/dev/null | /usr/bin/grep -q . || return 1 done ) task4_review1_preflight_and_lineage_gate() ( local database="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}" validation_case export TASK4_REVIEW1_CONSUMER_LOG="${fixture_root}/task4-review1-gate-consumer.log" export TASK4_REVIEW1_PRODUCER_LOG="${fixture_root}/task4-review1-gate-producer.log" export TASK4_REVIEW1_COMMAND_LOG="${fixture_root}/task4-review1-gate-command.log" export TASK4_REVIEW1_DB_STAT_LOG="${fixture_root}/task4-review1-gate-db-stat.log" _k3slr_command() { task4_review1_command_fake "$@"; } TASK4_REVIEW1_PRODUCER_CASE=success export TASK4_REVIEW1_PRODUCER_CASE for validation_case in non_tty parent_symlink; do : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_COMMAND_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" TASK4_REVIEW1_VALIDATION_CASE="$validation_case" export TASK4_REVIEW1_VALIDATION_CASE assert_fails _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" [[ ! -s "$TASK4_REVIEW1_PRODUCER_LOG" && ! -s "$TASK4_REVIEW1_CONSUMER_LOG" ]] || return 1 done : >"$TASK4_REVIEW1_CONSUMER_LOG" : >"$TASK4_REVIEW1_PRODUCER_LOG" : >"$TASK4_REVIEW1_DB_STAT_LOG" TASK4_REVIEW1_VALIDATION_CASE=post_drift export TASK4_REVIEW1_VALIDATION_CASE assert_fails _k3slr_luks_open_from_keepass "$database" "$K3SLR_KEEPASS_ENTRY" /dev/loop7 "$K3SLR_MAPPING_NAME" [[ "$(/usr/bin/wc -l <"$TASK4_REVIEW1_PRODUCER_LOG")" -eq 1 && ! -s "$TASK4_REVIEW1_CONSUMER_LOG" ]] ) task4_review1_exact_constants_become_readonly() ( local assignment /usr/bin/bash -c ' K3SLR_KEEPASS_ENTRY="K3s Recovery LUKS" K3SLR_KEEPASS_HEADER_ATTACHMENT="luks-header-backup.bin" _K3SLR_INT64_MAX=9223372036854775807 _K3SLR_REQUIRED_MINIMUM_FREE_BYTES=10737418240 source "$1" ' task4-readonly "$LIBRARY_PATH" for assignment in 'K3SLR_KEEPASS_ENTRY=changed' \ 'K3SLR_KEEPASS_HEADER_ATTACHMENT=changed' '_K3SLR_INT64_MAX=1' \ '_K3SLR_REQUIRED_MINIMUM_FREE_BYTES=1'; do ! /usr/bin/bash -c ' K3SLR_KEEPASS_ENTRY="K3s Recovery LUKS" K3SLR_KEEPASS_HEADER_ATTACHMENT="luks-header-backup.bin" _K3SLR_INT64_MAX=9223372036854775807 _K3SLR_REQUIRED_MINIMUM_FREE_BYTES=10737418240 source "$1" eval "$2" ' task4-readonly "$LIBRARY_PATH" "$assignment" 2>/dev/null || return 1 done ) task4_review1_generated_password_exact_lf() ( # shellcheck source=/dev/null source "$FEASIBILITY_PATH" if ! printf '%s\n' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' | _k3slrf_validate_generated_password_stdin; then printf 'exact-lf valid rejected\n' >&2; return 1; fi if printf '%s' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' | _k3slrf_validate_generated_password_stdin; then printf 'exact-lf missing accepted\n' >&2; return 1; fi if printf '%s\n\n' 'Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9Kk0Ll1Mm2N' | _k3slrf_validate_generated_password_stdin; then printf 'exact-lf double accepted\n' >&2; return 1; fi ) task5a_wrapper_and_guard_contract() ( local wrapper main_name mode output status trace_output for wrapper in "$PREPARE_WRAPPER_PATH" "$OPEN_WRAPPER_PATH" "$CLOSE_WRAPPER_PATH"; do [[ -f "$wrapper" && ! -L "$wrapper" ]] || { printf 'missing Task 5A lifecycle wrapper: %s\n' "$wrapper" >&2 return 1 } assert_succeeds /usr/bin/bash -n "$wrapper" done for mode in prepare open close; do case "$mode" in prepare) wrapper="$PREPARE_WRAPPER_PATH" ;; open) wrapper="$OPEN_WRAPPER_PATH" ;; close) wrapper="$CLOSE_WRAPPER_PATH" ;; esac main_name="_k3slr_${mode}_main" output="$({ PATH=/tmp/untrusted-path LC_ALL=POSIX umask 022 set -- --execute # shellcheck source=/dev/null source "$wrapper" declare -F "$main_name" >/dev/null || return 1 declare -F _k3slr_wrapper_initial_guard >/dev/null || return 1 _k3slr_wrapper_initial_guard 0 '' >/dev/null 2>&1 && return 1 _k3slr_wrapper_initial_guard 1000 x >/dev/null 2>&1 && return 1 _k3slr_wrapper_initial_guard 1000 '' || return 1 printf '%s|%s|' "$PATH" "$LC_ALL" umask } 2>/dev/null)" || return 1 assert_eq '/usr/sbin:/usr/bin:/sbin:/bin|C|0077' "$output" \ "$mode wrapper establishes its early source-safe process guard" trace_output="${fixture_root}/task5a-${mode}-xtrace.stderr" if /usr/bin/bash -x "$wrapper" 2>"$trace_output"; then printf '%s wrapper accepted xtrace\n' "$mode" >&2 return 1 fi ! /usr/bin/grep -Fq -- 'scripts/lib/k3s-local-recovery.sh' "$trace_output" || { printf '%s wrapper discovered/sourced the repository before xtrace refusal\n' "$mode" >&2 return 1 } output="$({ # shellcheck source=/dev/null source "$wrapper" _k3slr_lifecycle_main() { (( $# == 2 )) || return 1 printf '%s|%s\n' "$1" "$2" } "$main_name" } 2>/dev/null)" || return 1 assert_eq "${mode}|dry-run" "$output" "$mode no-argument parser selects dry-run" for bad_args in '--help' '-h' '--execute --execute' '--unknown' 'positional'; do # This deliberate word split enumerates fixed test arguments, never input. # shellcheck disable=SC2086 if output="$({ source "$wrapper"; "$main_name" $bad_args; } 2>&1)"; then printf '%s wrapper accepted invalid argv: %s\n' "$mode" "$bad_args" >&2 return 1 else status=$? fi [[ "$status" == 2 && "$output" == *'Usage:'* ]] || { printf '%s wrapper invalid argv did not produce usage exit 2: %s\n' "$mode" "$bad_args" >&2 return 1 } done done assert_fails _k3slr_require_execute_tty ) task5a_prepare_provider_capability_gate() ( local log="${fixture_root}/task5a-prepare-capability.log" _k3slr_prove_header_restore() { :; } export -f _k3slr_prove_header_restore /usr/bin/bash -c ' source "$1" ! declare -F _k3slr_prove_header_restore >/dev/null ' task5a-provider-cleanup "$PREPARE_WRAPPER_PATH" || { printf 'prepare wrapper retained an inherited/exported provider\n' >&2 return 1 } unset -f _k3slr_prove_header_restore # shellcheck source=/dev/null source "$PREPARE_WRAPPER_PATH" _k3slr_load_contract() { (( $# == 1 )) || return 1 printf 'contract\n' >>"$log" } _k3slr_require_execute_tty() { printf 'tty\n' >>"$log" return 1 } _k3slr_lifecycle_step() { printf 'step:%s\n' "$1" >>"$log" return 1 } _k3slr_lifecycle_dispatch() { printf 'dispatch\n' >>"$log" return 1 } _k3slr_command() { printf 'unexpected-command:%s\n' "$*" >>"$log" return 97 } assert_fails _k3slr_prepare_main --execute assert_eq contract "$(<"$log")" \ 'prepare provider absence refuses after contract and before every later boundary' ) task5a_open_close_capability_gate() ( local mode wrapper main_name state missing_binary log output expected line local -a trusted_binaries=( /usr/bin/keepassxc-cli /usr/sbin/cryptsetup /usr/sbin/losetup /usr/sbin/blkid /usr/sbin/smartctl /usr/bin/mount /usr/bin/umount /usr/bin/findmnt /usr/bin/lsblk /usr/bin/readlink /usr/bin/stat /usr/bin/ntfsinfo /usr/bin/ntfs-3g.probe /usr/bin/fuser /usr/bin/sync /usr/bin/cmp /usr/bin/cat /usr/bin/sha256sum /usr/bin/mawk /usr/bin/od /usr/bin/bash /usr/bin/du /usr/bin/test /usr/bin/dd /usr/bin/id /usr/bin/ps /usr/bin/sleep /usr/bin/mkdir /usr/bin/dpkg-query /usr/bin/mktemp /usr/bin/rm /usr/bin/rmdir /usr/bin/sudo ) for mode in open close; do case "$mode" in open) wrapper="$OPEN_WRAPPER_PATH" ;; close) wrapper="$CLOSE_WRAPPER_PATH" ;; esac main_name="_k3slr_${mode}_main" log="${fixture_root}/task5a-${mode}-capability.log" : >"$log" # shellcheck source=/dev/null source "$wrapper" _k3slr_load_contract() { (( $# == 1 )) || return 1 printf 'contract\n' >>"$log" } _k3slr_query_package_state() { local destination_name="$1" package="$2" expected_version="$3" result printf 'package:%s:%s\n' "$package" "$expected_version" >>"$log" case "$package" in keepassxc) result="${TASK5A_KEEPASS_STATE:-installed}" ;; cryptsetup-bin) result="${TASK5A_CRYPTSETUP_STATE:-installed}" ;; *) return 1 ;; esac printf -v "$destination_name" '%s' "$result" } _k3slr_trusted_root_executable() { printf 'binary:%s\n' "$1" >>"$log" [[ -z "${TASK5A_MISSING_BINARY:-}" || "$1" != "$TASK5A_MISSING_BINARY" ]] } _k3slr_require_execute_tty() { printf 'tty\n' >>"$log" } _k3slr_lifecycle_dispatch() { printf 'dispatch:%s:%s\n' "$1" "$2" >>"$log" } _k3slr_command() { printf 'unexpected-command:%s\n' "$*" >>"$log" return 97 } TASK5A_KEEPASS_STATE=installed TASK5A_CRYPTSETUP_STATE=installed TASK5A_MISSING_BINARY='' assert_succeeds "$main_name" --execute expected=$'contract\npackage:keepassxc:2.7.6+dfsg.1-1build3\npackage:cryptsetup-bin:2:2.7.0-1ubuntu4.2' for line in "${trusted_binaries[@]}"; do expected+=$'\n'binary:"$line" done expected+=$'\ntty\ndispatch:'"$mode"':_k3slr_prove_header_restore' assert_eq "$expected" "$(<"$log")" \ "$mode gates packages and every trusted binary before TTY/dispatch" for state in absent mixed wrong malformed; do : >"$log" case "$state" in absent) TASK5A_KEEPASS_STATE=absent TASK5A_CRYPTSETUP_STATE=absent ;; mixed) TASK5A_KEEPASS_STATE=installed TASK5A_CRYPTSETUP_STATE=absent ;; wrong) TASK5A_KEEPASS_STATE=wrong-version TASK5A_CRYPTSETUP_STATE=installed ;; malformed) TASK5A_KEEPASS_STATE=malformed TASK5A_CRYPTSETUP_STATE=installed ;; esac assert_fails "$main_name" --execute output="$(<"$log")" expected=$'contract\npackage:keepassxc:2.7.6+dfsg.1-1build3\npackage:cryptsetup-bin:2:2.7.0-1ubuntu4.2' assert_eq "$expected" "$output" "$mode $state package state stops before binary/TTY/dispatch" done : >"$log" TASK5A_KEEPASS_STATE=installed TASK5A_CRYPTSETUP_STATE=installed TASK5A_MISSING_BINARY=/usr/bin/stat assert_fails "$main_name" --execute output="$(<"$log")" [[ "$output" == *$'binary:/usr/bin/stat' && "$output" != *$'\ntty' && "$output" != *$'\ndispatch:' && "$output" != *'unexpected-command:'* ]] || { printf '%s missing binary did not stop at the capability boundary\n' "$mode" >&2 return 1 } done ) task5a_lifecycle_dispatch_contract() ( local mode step failure_step log expected actual local -a prepare_steps=( sudo context device-validator estimate-phase capacity-preflight confirm-prepare install-packages feasibility revalidate-device mount-outer revalidate-capacity create-layout create-kdbx create-container validate-allocation create-loop luks-format luks-open mkfs-ext4 mount-inner write-marker-metadata header-proof close-inner close-mapping detach-loop unmount-outer closed-validator ) local -a open_steps=( sudo context confirm-open 'device-validator(--expect-device-ready)' revalidate-context-device mount-outer validate-layout-kdbx-container validate-allocation-and-ntfs-attributes validate-outer-metadata collision-check create-loop luks-open mount-inner validate-inner-metadata-marker-capacity 'open-validator(--expect-open)' ) local -a close_steps=( sudo context confirm-close 'open-validator(--expect-open)' validate-metadata-and-full-lineage busy-process-query-pass-1 busy-process-query-pass-2 sync-inner-filesystem unmount-inner close-main-mapping detach-original-loop unmount-outer 'closed-validator(--expect-closed)' ) log="${fixture_root}/task5a-dispatch.log" _k3slr_lifecycle_step() { (( $# == 1 )) || return 1 printf 'hook:%s\n' "$1" >>"$log" } _k3slr_lifecycle_action() { case "$1" in prepare) (( $# == 3 )) || return 1 [[ "$3" == _k3slr_prepare_invocation ]] || return 1 ;; open|close) (( $# == 2 )) || return 1 ;; *) return 1 ;; esac printf 'action:%s\n' "$2" >>"$log" [[ -z "${failure_step:-}" || "$2" != "$failure_step" ]] } task5a_fixture_header_provider() { return 1 } for mode in prepare open close; do local -a steps=() case "$mode" in prepare) steps=("${prepare_steps[@]}") ;; open) steps=("${open_steps[@]}") ;; close) steps=("${close_steps[@]}") ;; esac : >"$log" failure_step='' assert_succeeds _k3slr_lifecycle_dispatch "$mode" task5a_fixture_header_provider expected='' for step in "${steps[@]}"; do [[ -z "$expected" ]] || expected+=$'\n' expected+="hook:${step}"$'\n'"action:${step}" done actual="$(<"$log")" assert_eq "$expected" "$actual" "$mode emits the independent exact lifecycle order" case "$mode" in prepare) failure_step=create-loop ;; open) failure_step=collision-check ;; close) failure_step=unmount-inner ;; esac : >"$log" assert_fails _k3slr_lifecycle_dispatch "$mode" task5a_fixture_header_provider expected='' for step in "${steps[@]}"; do [[ -z "$expected" ]] || expected+=$'\n' expected+="hook:${step}"$'\n'"action:${step}" [[ "$step" == "$failure_step" ]] && break done actual="$(<"$log")" assert_eq "$expected" "$actual" \ "$mode failure includes its own hook/action and dispatches no later action" done ) task5a_production_actions_fail_closed() ( local lifecycle step command_calls=0 action_rc=0 probe_rc=0 record='' local -a prepare_unavailable=( context estimate-phase capacity-preflight confirm-prepare install-packages feasibility revalidate-device mount-outer revalidate-capacity create-layout create-kdbx create-container validate-allocation create-loop luks-format luks-open mkfs-ext4 mount-inner write-marker-metadata header-proof close-inner close-mapping detach-loop unmount-outer closed-validator ) local -a open_unavailable=( sudo context confirm-open 'device-validator(--expect-device-ready)' revalidate-context-device mount-outer validate-layout-kdbx-container validate-allocation-and-ntfs-attributes validate-outer-metadata collision-check create-loop luks-open mount-inner validate-inner-metadata-marker-capacity 'open-validator(--expect-open)' ) local -a close_unavailable=( sudo context confirm-close 'open-validator(--expect-open)' validate-metadata-and-full-lineage busy-process-query-pass-1 busy-process-query-pass-2 sync-inner-filesystem unmount-inner close-main-mapping detach-original-loop unmount-outer 'closed-validator(--expect-closed)' ) _k3slr_command() { (( command_calls += 1 )) return 97 } task5b_unavailable_probe_payload() { local lifecycle="${1-}" step="${2-}" action_rc=0 command_calls=0 (( $# == 2 )) || return 1 if [[ "$lifecycle" == prepare ]]; then _k3slr_lifecycle_action prepare "$step" _k3slr_prepare_invocation || action_rc=$? else _k3slr_lifecycle_action "$lifecycle" "$step" || action_rc=$? fi task5b_production_probe_emit_ok unavailable "$action_rc" "$command_calls" } task5b_production_probe_adversary_matrix || return 1 task5b_production_probe_static_graph_is_closed unavailable || return 1 for lifecycle in prepare open close; do local -a actions=() case "$lifecycle" in prepare) actions=("${prepare_unavailable[@]}") ;; open) actions=("${open_unavailable[@]}") ;; close) actions=("${close_unavailable[@]}") ;; esac for step in "${actions[@]}"; do record='' probe_rc=0 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm unavailable || return 1 task5b_unavailable_probe_payload "$lifecycle" "$step" )" || probe_rc=$? assert_eq 0 "$probe_rc" \ "historical unavailable probe completes safely: $lifecycle/$step" assert_eq 'TASK5B_PROBE_OK|unavailable|1|0' "$record" \ "historical unavailable action is fail-closed before commands: $lifecycle/$step" done done ) task5a_unowned_capture_cleanup_is_rejected() ( local description="$1" capture_directory capture_record local rm_rc=0 rmdir_rc=0 records_preserved=0 directory_preserved=0 capture_directory="$(/usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 [[ "$capture_directory" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8}$ && -d "$capture_directory" && ! -L "$capture_directory" ]] || return 1 : >"${capture_directory}/stdout" : >"${capture_directory}/stderr" _k3slr_command /usr/bin/rm -- "${capture_directory}/stdout" \ "${capture_directory}/stderr" >/dev/null 2>&1 || rm_rc=$? if [[ -f "${capture_directory}/stdout" && ! -L "${capture_directory}/stdout" && -f "${capture_directory}/stderr" && ! -L "${capture_directory}/stderr" ]]; then records_preserved=1 fi for capture_record in "${capture_directory}/stdout" "${capture_directory}/stderr"; do if [[ -f "$capture_record" && ! -L "$capture_record" ]]; then /usr/bin/rm -- "$capture_record" || return 1 elif [[ -e "$capture_record" || -L "$capture_record" ]]; then return 1 fi done _k3slr_command /usr/bin/rmdir -- "$capture_directory" \ >/dev/null 2>&1 || rmdir_rc=$? if [[ -d "$capture_directory" && ! -L "$capture_directory" ]]; then directory_preserved=1 /usr/bin/rmdir -- "$capture_directory" || return 1 elif [[ -e "$capture_directory" || -L "$capture_directory" ]]; then return 1 fi assert_eq 97 "$rm_rc" "$description rejects unowned record deletion before execution" assert_eq 1 "$records_preserved" "$description preserves unowned capture records" assert_eq 97 "$rmdir_rc" "$description rejects unowned directory deletion before execution" assert_eq 1 "$directory_preserved" "$description preserves the unowned capture directory" ) task5a_capture_path_is_safe() { (( $# == 1 )) && [[ "$1" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8}$ ]] } task5a_capture_directory_identity() { local capture_directory="${1-}" identity (( $# == 1 )) && task5a_capture_path_is_safe "$capture_directory" && [[ -d "$capture_directory" && ! -L "$capture_directory" ]] || return 97 identity="$(/usr/bin/stat --format='%d:%i' -- "$capture_directory")" || return 97 [[ "$identity" =~ ^[0-9]+:[0-9]+$ ]] || return 97 printf '%s\n' "$identity" } task5a_capture_log_read_exact() { local log="${1-}" destination_name="${2-}" size line probe local row_count=0 (( $# == 2 )) && [[ -f "$log" && ! -L "$log" ]] && [[ "$destination_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || return 97 local -n destination="$destination_name" destination=() size="$(/usr/bin/stat --format='%s' -- "$log")" || return 97 [[ "$size" =~ ^[0-9]+$ ]] && (( size <= 1048576 )) || return 97 if IFS= read -r -d '' probe <"$log"; then return 97 fi while :; do line='' if IFS= read -r line; then row_count=$((row_count + 1)) (( row_count <= 4096 && ${#line} > 0 && ${#line} <= 512 )) || return 97 [[ "$line" != *$'\r'* ]] || return 97 destination+=("$line") else [[ -z "$line" ]] || return 97 break fi done <"$log" } task5a_capture_ownership_log_require() { local ownership_log="${1-}" capture_directory="${2-}" expected_phase="${3-}" local row event path identity active_path='' active_identity='' active_phase='' local target_phase=absent target_identity='' local -a rows=() local -A phase_by_path=() identity_by_path=() (( $# == 3 )) || return 97 case "$expected_phase" in complete) [[ -z "$capture_directory" ]] || return 97 ;; absent|created|records-removed|removed) task5a_capture_path_is_safe "$capture_directory" || return 97 ;; *) return 97 ;; esac task5a_capture_log_read_exact "$ownership_log" rows || return 97 for row in "${rows[@]}"; do [[ "$row" =~ ^(created|records-removed|removed)\|(/tmp/k3slr-capture\.[A-Za-z0-9]{8})\|([0-9]+:[0-9]+)$ ]] || return 97 event="${BASH_REMATCH[1]}" path="${BASH_REMATCH[2]}" identity="${BASH_REMATCH[3]}" case "$event" in created) [[ -z "$active_path" && -z "${phase_by_path[$path]+present}" ]] || return 97 active_path="$path" active_identity="$identity" active_phase=created phase_by_path["$path"]=created identity_by_path["$path"]="$identity" ;; records-removed) [[ "$active_path" == "$path" && "$active_phase" == created && "$active_identity" == "$identity" ]] || return 97 active_phase=records-removed phase_by_path["$path"]=records-removed ;; removed) [[ "$active_path" == "$path" && "$active_phase" == records-removed && "$active_identity" == "$identity" ]] || return 97 phase_by_path["$path"]=removed active_path='' active_identity='' active_phase='' ;; esac done if [[ -n "$capture_directory" && -n "${phase_by_path[$capture_directory]+present}" ]]; then target_phase="${phase_by_path[$capture_directory]}" target_identity="${identity_by_path[$capture_directory]}" fi case "$expected_phase" in complete) [[ -z "$active_path" ]] ;; absent) [[ "$target_phase" == absent && -z "$active_path" ]] ;; created|records-removed) [[ "$target_phase" == "$expected_phase" && "$active_path" == "$capture_directory" ]] ;; removed) [[ "$target_phase" == removed && -z "$active_path" ]] ;; esac || return 97 [[ "$expected_phase" == complete || "$expected_phase" == absent ]] || printf '%s\n' "$target_identity" } task5a_capture_event_log_require() { local event_log="${1-}" capture_directory="${2-}" expected_phase="${3-}" local row event path command_id active_path='' active_phase='' target_phase=absent local command_count=0 local -a rows=() local -A phase_by_path=() (( $# == 3 )) || return 97 case "$expected_phase" in complete) [[ -z "$capture_directory" ]] || return 97 ;; absent|created|records-removed|removed) task5a_capture_path_is_safe "$capture_directory" || return 97 ;; *) return 97 ;; esac task5a_capture_log_read_exact "$event_log" rows || return 97 for row in "${rows[@]}"; do if [[ "$row" =~ ^(mktemp|rm|rmdir)\|(/tmp/k3slr-capture\.[A-Za-z0-9]{8})$ ]]; then event="${BASH_REMATCH[1]}" path="${BASH_REMATCH[2]}" case "$event" in mktemp) [[ -z "$active_path" && -z "${phase_by_path[$path]+present}" ]] || return 97 active_path="$path" active_phase=created command_count=0 phase_by_path["$path"]=created ;; rm) [[ "$active_path" == "$path" && "$active_phase" == created ]] || return 97 active_phase=records-removed phase_by_path["$path"]=records-removed ;; rmdir) [[ "$active_path" == "$path" && "$active_phase" == records-removed ]] || return 97 phase_by_path["$path"]=removed active_path='' active_phase='' command_count=0 ;; esac elif [[ "$row" =~ ^command\|([A-Za-z0-9][A-Za-z0-9._:-]{0,63})$ ]]; then command_id="${BASH_REMATCH[1]}" [[ -n "$command_id" && -n "$active_path" && "$active_phase" == created && "$command_count" == 0 ]] || return 97 command_count=1 else return 97 fi done if [[ -n "$capture_directory" && -n "${phase_by_path[$capture_directory]+present}" ]]; then target_phase="${phase_by_path[$capture_directory]}" fi case "$expected_phase" in complete) [[ -z "$active_path" ]] ;; absent) [[ "$target_phase" == absent && -z "$active_path" ]] ;; created|records-removed) [[ "$target_phase" == "$expected_phase" && "$active_path" == "$capture_directory" ]] ;; removed) [[ "$target_phase" == removed && -z "$active_path" ]] ;; esac || return 97 } task5a_capture_ledgers_require_coherent() { local ownership_log="${1-}" event_log="${2-}" capture_directory="${3-}" local expected_phase="${4-}" ownership_identity row phase path entry_index local -a ownership_rows=() event_rows=() local -a ownership_entries=() event_entries=() (( $# == 4 )) || return 97 ownership_identity="$(task5a_capture_ownership_log_require \ "$ownership_log" "$capture_directory" "$expected_phase")" || return 97 task5a_capture_event_log_require \ "$event_log" "$capture_directory" "$expected_phase" || return 97 task5a_capture_log_read_exact "$ownership_log" ownership_rows || return 97 task5a_capture_log_read_exact "$event_log" event_rows || return 97 for row in "${ownership_rows[@]}"; do [[ "$row" =~ ^(created|records-removed|removed)\|(/tmp/k3slr-capture\.[A-Za-z0-9]{8})\|[0-9]+:[0-9]+$ ]] || return 97 ownership_entries+=("${BASH_REMATCH[1]}|${BASH_REMATCH[2]}") done for row in "${event_rows[@]}"; do if [[ "$row" =~ ^(mktemp|rm|rmdir)\|(/tmp/k3slr-capture\.[A-Za-z0-9]{8})$ ]]; then case "${BASH_REMATCH[1]}" in mktemp) phase=created ;; rm) phase=records-removed ;; rmdir) phase=removed ;; *) return 97 ;; esac path="${BASH_REMATCH[2]}" event_entries+=("${phase}|${path}") elif [[ "$row" =~ ^command\|[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$ ]]; then continue else return 97 fi done (( ${#ownership_entries[@]} == ${#event_entries[@]} )) || return 97 for (( entry_index=0; entry_index<${#ownership_entries[@]}; entry_index++ )); do [[ "${ownership_entries[entry_index]}" == "${event_entries[entry_index]}" ]] || return 97 done case "$expected_phase" in created|records-removed|removed) printf '%s\n' "$ownership_identity" ;; esac } task5a_capture_ledgers_allow_registration() { local ownership_log="${1-}" event_log="${2-}" (( $# == 2 )) || return 97 task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" '' complete } task5a_capture_ledger_register() { local ownership_log="${1-}" event_log="${2-}" capture_directory="${3-}" local current_identity (( $# == 3 )) && task5a_capture_path_is_safe "$capture_directory" || return 97 task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" "$capture_directory" absent || return 97 current_identity="$(task5a_capture_directory_identity "$capture_directory")" || return 97 printf 'created|%s|%s\n' "$capture_directory" "$current_identity" >>"$ownership_log" || return 97 printf 'mktemp|%s\n' "$capture_directory" >>"$event_log" || return 97 task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" "$capture_directory" created >/dev/null || return 97 } task5a_capture_ledger_require() { task5a_capture_ownership_log_require "$@" } task5a_capture_event_phase_is() { task5a_capture_event_log_require "$@" } task5a_remove_owned_capture_records() { local ownership_log="${1-}" event_log="${2-}" capture_directory pinned_identity local current_identity shift 2 || return 97 (( $# == 4 )) && [[ "$1" == /usr/bin/rm && "$2" == -- && "$3" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8}/stdout$ ]] || return 97 capture_directory="${3%/stdout}" [[ "$4" == "${capture_directory}/stderr" && -d "$capture_directory" && ! -L "$capture_directory" && -f "$3" && ! -L "$3" && -f "$4" && ! -L "$4" ]] || return 97 pinned_identity="$(task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" "$capture_directory" created)" || return 97 current_identity="$(task5a_capture_directory_identity "$capture_directory")" || return 97 [[ "$current_identity" == "$pinned_identity" ]] || return 97 "$@" || return $? [[ ! -e "$3" && ! -L "$3" && ! -e "$4" && ! -L "$4" ]] || return 1 printf 'records-removed|%s|%s\n' "$capture_directory" "$pinned_identity" \ >>"$ownership_log" || return 97 printf 'rm|%s\n' "$capture_directory" >>"$event_log" || return 97 task5a_capture_ledgers_require_coherent "$ownership_log" "$event_log" \ "$capture_directory" records-removed >/dev/null || return 97 } task5a_remove_owned_capture_directory() { local ownership_log="${1-}" event_log="${2-}" capture_directory pinned_identity local current_identity shift 2 || return 97 (( $# == 3 )) && [[ "$1" == /usr/bin/rmdir && "$2" == -- ]] || return 97 capture_directory="$3" task5a_capture_path_is_safe "$capture_directory" && [[ -d "$capture_directory" && ! -L "$capture_directory" ]] || return 97 pinned_identity="$(task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" "$capture_directory" records-removed)" || return 97 current_identity="$(task5a_capture_directory_identity "$capture_directory")" || return 97 [[ "$current_identity" == "$pinned_identity" ]] || return 97 "$@" || return $? [[ ! -e "$capture_directory" && ! -L "$capture_directory" ]] || return 1 printf 'removed|%s|%s\n' "$capture_directory" "$pinned_identity" \ >>"$ownership_log" || return 97 printf 'rmdir|%s\n' "$capture_directory" >>"$event_log" || return 97 task5a_capture_ledgers_require_coherent "$ownership_log" "$event_log" \ "$capture_directory" removed >/dev/null || return 97 } task5a_cleanup_capture_probe_path() { local capture_directory="${1-}" capture_record (( $# == 1 )) && task5a_capture_path_is_safe "$capture_directory" || return 1 if [[ ! -e "$capture_directory" && ! -L "$capture_directory" ]]; then return 0 fi [[ -d "$capture_directory" && ! -L "$capture_directory" ]] || return 1 for capture_record in "${capture_directory}/stdout" "${capture_directory}/stderr"; do if [[ -f "$capture_record" && ! -L "$capture_record" ]]; then /usr/bin/rm -- "$capture_record" || return 1 elif [[ -e "$capture_record" || -L "$capture_record" ]]; then return 1 fi done /usr/bin/rmdir -- "$capture_directory" } task5a_capture_cleanup_phase_and_identity_is_enforced() ( local description="$1" early_path repeat_path drift_path replacement_path local early_rmdir_rc=0 early_rm_rc=0 early_finish_rmdir_rc=0 local repeat_rm_rc=0 repeat_second_rm_rc=0 repeat_rmdir_rc=0 local reuse_rm_rc=0 reuse_rmdir_rc=0 drift_rm_rc=0 drift_rmdir_rc=0 local early_preserved=0 early_completed=0 repeat_records_preserved=0 repeat_removed=0 local reuse_records_preserved=0 reuse_directory_preserved=0 local drift_records_preserved=0 drift_directory_preserved=0 local drift_identity replacement_identity early_path="$(_k3slr_command /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 task5a_capture_path_is_safe "$early_path" && [[ -d "$early_path" && ! -L "$early_path" ]] || return 1 _k3slr_command /usr/bin/rmdir -- "$early_path" \ >/dev/null 2>&1 || early_rmdir_rc=$? [[ -d "$early_path" && ! -L "$early_path" ]] && early_preserved=1 : >"${early_path}/stdout" : >"${early_path}/stderr" _k3slr_command /usr/bin/rm -- "${early_path}/stdout" \ "${early_path}/stderr" >/dev/null 2>&1 || early_rm_rc=$? _k3slr_command /usr/bin/rmdir -- "$early_path" \ >/dev/null 2>&1 || early_finish_rmdir_rc=$? [[ ! -e "$early_path" && ! -L "$early_path" ]] && early_completed=1 task5a_cleanup_capture_probe_path "$early_path" || return 1 repeat_path="$(_k3slr_command /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 task5a_capture_path_is_safe "$repeat_path" && [[ -d "$repeat_path" && ! -L "$repeat_path" ]] || return 1 : >"${repeat_path}/stdout" : >"${repeat_path}/stderr" _k3slr_command /usr/bin/rm -- "${repeat_path}/stdout" \ "${repeat_path}/stderr" >/dev/null 2>&1 || repeat_rm_rc=$? : >"${repeat_path}/stdout" : >"${repeat_path}/stderr" _k3slr_command /usr/bin/rm -- "${repeat_path}/stdout" \ "${repeat_path}/stderr" >/dev/null 2>&1 || repeat_second_rm_rc=$? if [[ -f "${repeat_path}/stdout" && ! -L "${repeat_path}/stdout" && -f "${repeat_path}/stderr" && ! -L "${repeat_path}/stderr" ]]; then repeat_records_preserved=1 fi /usr/bin/rm -- "${repeat_path}/stdout" "${repeat_path}/stderr" || return 1 _k3slr_command /usr/bin/rmdir -- "$repeat_path" \ >/dev/null 2>&1 || repeat_rmdir_rc=$? if [[ ! -e "$repeat_path" && ! -L "$repeat_path" ]]; then repeat_removed=1 /usr/bin/mkdir -- "$repeat_path" || return 1 fi [[ -d "$repeat_path" && ! -L "$repeat_path" ]] || return 1 : >"${repeat_path}/stdout" : >"${repeat_path}/stderr" _k3slr_command /usr/bin/rm -- "${repeat_path}/stdout" \ "${repeat_path}/stderr" >/dev/null 2>&1 || reuse_rm_rc=$? if [[ -f "${repeat_path}/stdout" && ! -L "${repeat_path}/stdout" && -f "${repeat_path}/stderr" && ! -L "${repeat_path}/stderr" ]]; then reuse_records_preserved=1 fi /usr/bin/rm -- "${repeat_path}/stdout" "${repeat_path}/stderr" || return 1 _k3slr_command /usr/bin/rmdir -- "$repeat_path" \ >/dev/null 2>&1 || reuse_rmdir_rc=$? [[ -d "$repeat_path" && ! -L "$repeat_path" ]] && reuse_directory_preserved=1 task5a_cleanup_capture_probe_path "$repeat_path" || return 1 drift_path="$(_k3slr_command /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 replacement_path="$(/usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 task5a_capture_path_is_safe "$drift_path" && task5a_capture_path_is_safe "$replacement_path" || return 1 drift_identity="$(task5a_capture_directory_identity "$drift_path")" || return 1 replacement_identity="$(task5a_capture_directory_identity "$replacement_path")" || return 1 [[ "$drift_identity" != "$replacement_identity" ]] || return 1 /usr/bin/rmdir -- "$drift_path" || return 1 /usr/bin/mv -- "$replacement_path" "$drift_path" || return 1 [[ -d "$drift_path" && ! -L "$drift_path" ]] || return 1 : >"${drift_path}/stdout" : >"${drift_path}/stderr" _k3slr_command /usr/bin/rm -- "${drift_path}/stdout" \ "${drift_path}/stderr" >/dev/null 2>&1 || drift_rm_rc=$? if [[ -f "${drift_path}/stdout" && ! -L "${drift_path}/stdout" && -f "${drift_path}/stderr" && ! -L "${drift_path}/stderr" ]]; then drift_records_preserved=1 fi /usr/bin/rm -- "${drift_path}/stdout" "${drift_path}/stderr" || return 1 _k3slr_command /usr/bin/rmdir -- "$drift_path" \ >/dev/null 2>&1 || drift_rmdir_rc=$? [[ -d "$drift_path" && ! -L "$drift_path" ]] && drift_directory_preserved=1 task5a_cleanup_capture_probe_path "$drift_path" || return 1 assert_eq 97 "$early_rmdir_rc" "$description rejects rmdir before record removal" assert_eq 1 "$early_preserved" "$description preserves an early-rmdir owned directory" assert_eq 0 "$early_rm_rc" "$description completes record removal after early rmdir rejection" assert_eq 0 "$early_finish_rmdir_rc" \ "$description completes rmdir after early rmdir rejection" assert_eq 1 "$early_completed" "$description completes the early-rmdir capture lifecycle" assert_eq 0 "$repeat_rm_rc" "$description permits the first owned record removal" assert_eq 97 "$repeat_second_rm_rc" "$description rejects repeated owned record removal" assert_eq 1 "$repeat_records_preserved" "$description preserves records on repeated rm" assert_eq 0 "$repeat_rmdir_rc" "$description permits rmdir after one successful rm" assert_eq 1 "$repeat_removed" "$description removes a completed owned directory" assert_eq 97 "$reuse_rm_rc" "$description rejects completed-path reuse for rm" assert_eq 1 "$reuse_records_preserved" "$description preserves reused-path records" assert_eq 97 "$reuse_rmdir_rc" "$description rejects completed-path reuse for rmdir" assert_eq 1 "$reuse_directory_preserved" "$description preserves a reused directory" assert_eq 97 "$drift_rm_rc" "$description rejects identity drift for rm" assert_eq 1 "$drift_records_preserved" "$description preserves identity-drifted records" assert_eq 97 "$drift_rmdir_rc" "$description rejects identity drift for rmdir" assert_eq 1 "$drift_directory_preserved" "$description preserves an identity-drifted directory" ) task5a_global_corrupt_capture_ledgers_are_rejected() ( local ownership_log="$1" event_log="$2" description="$3" target_path foreign_path local rm_rc=0 records_preserved=0 local -a foreign_paths=( /tmp/k3slr-capture.R5A00001 /tmp/k3slr-capture.R5A00002 /tmp/k3slr-capture.R5A00003 /tmp/k3slr-capture.R5A00004 ) target_path="$(_k3slr_command /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 task5a_capture_path_is_safe "$target_path" && [[ -d "$target_path" && ! -L "$target_path" ]] || return 1 for foreign_path in "${foreign_paths[@]}"; do [[ "$target_path" != "$foreign_path" ]] || return 1 done : >"${target_path}/stdout" : >"${target_path}/stderr" printf '%s\n' \ 'removed|/tmp/k3slr-capture.R5A00001|101:101' \ 'created|/tmp/k3slr-capture.R5A00002|102:102' \ 'created|/tmp/k3slr-capture.R5A00002|102:102' \ 'created|/tmp/k3slr-capture.R5A00003|103:103' \ 'records-removed|/tmp/k3slr-capture.R5A00003|104:104' \ >>"$ownership_log" printf '%s' 'created|/tmp/k3slr-capture.R5A00004|105:105' >>"$ownership_log" printf '%s\n' \ 'rm|/tmp/k3slr-capture.R5A00001' \ 'mktemp|/tmp/k3slr-capture.R5A00002' \ 'mktemp|/tmp/k3slr-capture.R5A00002' \ 'malformed-event-row' \ >>"$event_log" printf '%s' 'command|unterminated-probe' >>"$event_log" _k3slr_command /usr/bin/rm -- "${target_path}/stdout" \ "${target_path}/stderr" >/dev/null 2>&1 || rm_rc=$? if [[ -f "${target_path}/stdout" && ! -L "${target_path}/stdout" && -f "${target_path}/stderr" && ! -L "${target_path}/stderr" ]]; then records_preserved=1 fi task5a_cleanup_capture_probe_path "$target_path" || return 1 assert_eq 97 "$rm_rc" "$description rejects globally corrupt ledgers before deletion" assert_eq 1 "$records_preserved" "$description preserves records for global ledger corruption" ) task5a_cross_ledger_coherence_is_enforced() ( local ownership_log="$1" event_log="$2" path_log="$3" description="$4" local registration_output='' registration_rc=0 registration_orphan=0 local rm_target rmdir_target target_identity rm_rc=0 rmdir_rc=0 local rm_preserved=0 rmdir_preserved=0 path_index local -a registration_paths_before=() registration_paths_after=() local -a path_log_before=() path_log_after=() : >"$ownership_log" : >"$event_log" : >"$path_log" printf '%s\n' \ 'created|/tmp/k3slr-capture.R6OWN001|601:701' \ 'records-removed|/tmp/k3slr-capture.R6OWN001|601:701' \ 'removed|/tmp/k3slr-capture.R6OWN001|601:701' >"$ownership_log" printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6EVT001' \ 'rm|/tmp/k3slr-capture.R6EVT001' \ 'rmdir|/tmp/k3slr-capture.R6EVT001' >"$event_log" shopt -s nullglob registration_paths_before=(/tmp/k3slr-capture.????????) mapfile -t path_log_before <"$path_log" registration_output="$(_k3slr_command /usr/bin/mktemp --directory \ --tmpdir=/tmp k3slr-capture.XXXXXXXX)" || registration_rc=$? registration_paths_after=(/tmp/k3slr-capture.????????) mapfile -t path_log_after <"$path_log" if (( ${#registration_paths_before[@]} != ${#registration_paths_after[@]} )); then registration_orphan=1 else for (( path_index=0; path_index<${#registration_paths_before[@]}; path_index++ )); do if [[ "${registration_paths_before[path_index]}" != \ "${registration_paths_after[path_index]}" ]]; then registration_orphan=1 fi done fi if [[ -n "$registration_output" ]]; then task5a_capture_path_is_safe "$registration_output" || return 1 task5a_cleanup_capture_probe_path "$registration_output" || return 1 fi rm_target="$(/usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 target_identity="$(task5a_capture_directory_identity "$rm_target")" || return 1 : >"${rm_target}/stdout" : >"${rm_target}/stderr" printf '%s\n' \ 'created|/tmp/k3slr-capture.R6OWN002|602:702' \ 'records-removed|/tmp/k3slr-capture.R6OWN002|602:702' \ 'removed|/tmp/k3slr-capture.R6OWN002|602:702' \ "created|${rm_target}|${target_identity}" >"$ownership_log" printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6EVT002' \ 'rm|/tmp/k3slr-capture.R6EVT002' \ 'rmdir|/tmp/k3slr-capture.R6EVT002' \ "mktemp|${rm_target}" >"$event_log" _k3slr_command /usr/bin/rm -- "${rm_target}/stdout" \ "${rm_target}/stderr" >/dev/null 2>&1 || rm_rc=$? if [[ -f "${rm_target}/stdout" && ! -L "${rm_target}/stdout" && -f "${rm_target}/stderr" && ! -L "${rm_target}/stderr" ]]; then rm_preserved=1 fi task5a_cleanup_capture_probe_path "$rm_target" || return 1 rmdir_target="$(/usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 target_identity="$(task5a_capture_directory_identity "$rmdir_target")" || return 1 printf '%s\n' \ 'created|/tmp/k3slr-capture.R6OWN003|603:703' \ 'records-removed|/tmp/k3slr-capture.R6OWN003|603:703' \ 'removed|/tmp/k3slr-capture.R6OWN003|603:703' \ "created|${rmdir_target}|${target_identity}" \ "records-removed|${rmdir_target}|${target_identity}" >"$ownership_log" printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6EVT003' \ 'rm|/tmp/k3slr-capture.R6EVT003' \ 'rmdir|/tmp/k3slr-capture.R6EVT003' \ "mktemp|${rmdir_target}" \ "rm|${rmdir_target}" >"$event_log" _k3slr_command /usr/bin/rmdir -- "$rmdir_target" \ >/dev/null 2>&1 || rmdir_rc=$? [[ -d "$rmdir_target" && ! -L "$rmdir_target" ]] && rmdir_preserved=1 task5a_cleanup_capture_probe_path "$rmdir_target" || return 1 assert_eq 97 "$registration_rc" \ "$description rejects registration for individually complete mismatched ledgers" assert_eq '' "$registration_output" \ "$description emits no mktemp output for mismatched completed histories" assert_eq 0 "$registration_orphan" \ "$description creates no real mktemp directory for mismatched completed histories" assert_eq "${#path_log_before[@]}" "${#path_log_after[@]}" \ "$description does not register a path for mismatched completed histories" assert_eq 97 "$rm_rc" \ "$description rejects rm for mismatched history plus the same active target" assert_eq 1 "$rm_preserved" \ "$description preserves records for mismatched history plus the same active target" assert_eq 97 "$rmdir_rc" \ "$description rejects rmdir for mismatched history plus the same active target" assert_eq 1 "$rmdir_preserved" \ "$description preserves the directory for mismatched history plus the same active target" ) task5a_command_append_failure_is_rejected() ( local append_target="$1" ownership_log="$2" event_log="$3" path_log="$4" local description="$5" capture_path original_mode output='' command_rc=0 local target_preserved=0 row_index local -a append_before=() append_after=() local -a ownership_before=() ownership_after=() local -a event_before=() event_after=() local -a paths_before=() paths_after=() shift 5 || return 1 (( $# > 0 )) && [[ -f "$append_target" && ! -L "$append_target" ]] || return 1 original_mode="$(/usr/bin/stat --format='%a' -- "$append_target")" || return 1 [[ "$original_mode" =~ ^[0-7]{3,4}$ ]] || return 1 capture_path="$(_k3slr_command /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || return 1 task5a_capture_path_is_safe "$capture_path" && [[ -d "$capture_path" && ! -L "$capture_path" ]] || return 1 task5a_append_failure_cleanup() { /usr/bin/chmod "$original_mode" -- "$append_target" >/dev/null 2>&1 || : task5a_cleanup_capture_probe_path "$capture_path" >/dev/null 2>&1 || : } trap task5a_append_failure_cleanup EXIT mapfile -t append_before <"$append_target" mapfile -t ownership_before <"$ownership_log" mapfile -t event_before <"$event_log" mapfile -t paths_before <"$path_log" /usr/bin/chmod 0400 -- "$append_target" || return 1 output="$(_k3slr_command "$@" 2>/dev/null)" || command_rc=$? /usr/bin/chmod "$original_mode" -- "$append_target" || return 1 mapfile -t append_after <"$append_target" mapfile -t ownership_after <"$ownership_log" mapfile -t event_after <"$event_log" mapfile -t paths_after <"$path_log" [[ -d "$capture_path" && ! -L "$capture_path" ]] && target_preserved=1 task5a_cleanup_capture_probe_path "$capture_path" || return 1 trap - EXIT assert_eq 97 "$command_rc" "$description returns rc 97 when its required audit append fails" assert_eq '' "$output" "$description does not enter the recognized command branch" assert_eq 1 "$target_preserved" "$description preserves the active capture target" assert_eq "${#append_before[@]}" "${#append_after[@]}" \ "$description preserves the append target row count" for (( row_index=0; row_index<${#append_before[@]}; row_index++ )); do assert_eq "${append_before[row_index]}" "${append_after[row_index]}" \ "$description preserves every append target row" done assert_eq "${#ownership_before[@]}" "${#ownership_after[@]}" \ "$description preserves the ownership ledger row count" for (( row_index=0; row_index<${#ownership_before[@]}; row_index++ )); do assert_eq "${ownership_before[row_index]}" "${ownership_after[row_index]}" \ "$description preserves every ownership ledger row" done assert_eq "${#event_before[@]}" "${#event_after[@]}" \ "$description appends no command event" for (( row_index=0; row_index<${#event_before[@]}; row_index++ )); do assert_eq "${event_before[row_index]}" "${event_after[row_index]}" \ "$description preserves every event ledger row" done assert_eq "${#paths_before[@]}" "${#paths_after[@]}" \ "$description preserves the path ledger row count" for (( row_index=0; row_index<${#paths_before[@]}; row_index++ )); do assert_eq "${paths_before[row_index]}" "${paths_after[row_index]}" \ "$description preserves every path ledger row" done ) task5a_capture_global_parser_contract() ( local ownership_log="${fixture_root}/task5a-parser-ownership.log" local event_log="${fixture_root}/task5a-parser-events.log" local target=/tmp/k3slr-capture.R5P00001 declare -F task5a_capture_ownership_log_require >/dev/null || return 1 declare -F task5a_capture_event_log_require >/dev/null || return 1 declare -F task5a_capture_ledgers_require_coherent >/dev/null || return 1 printf '%s\n' "created|${target}|201:301" >"$ownership_log" printf '%s\n' "mktemp|${target}" 'command|parser-probe' >"$event_log" assert_succeeds task5a_capture_ownership_log_require \ "$ownership_log" "$target" created >/dev/null assert_succeeds task5a_capture_event_log_require "$event_log" "$target" created printf 'created|%s|201:301\0\n' "$target" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf 'created|%s|201:301\r\n' "$target" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf '\n' >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf 'created|%s|201:301|extra\n' "$target" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf 'malformed-ownership-row\n' >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf 'created|%s|201:301' "$target" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf '%s\n' \ 'removed|/tmp/k3slr-capture.R5P00002|202:302' \ "created|${target}|201:301" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf '%s\n' \ 'created|/tmp/k3slr-capture.R5P00002|202:302' \ 'created|/tmp/k3slr-capture.R5P00002|202:302' \ "created|${target}|201:301" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf '%s\n' \ 'created|/tmp/k3slr-capture.R5P00002|202:302' \ 'records-removed|/tmp/k3slr-capture.R5P00002|203:303' \ "created|${target}|201:301" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf '%s\n' \ 'created|/tmp/k3slr-capture.R5P00002|202:302' \ "created|${target}|201:301" >"$ownership_log" assert_fails task5a_capture_ownership_log_require "$ownership_log" "$target" created printf 'mktemp|%s\0\n' "$target" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf 'mktemp|%s\r\n' "$target" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf '\n' >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf 'mktemp|%s|extra\n' "$target" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf 'malformed-event-row\n' >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf 'mktemp|%s' "$target" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf '%s\n' \ 'rm|/tmp/k3slr-capture.R5P00002' \ "mktemp|${target}" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R5P00002' \ 'mktemp|/tmp/k3slr-capture.R5P00002' \ "mktemp|${target}" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf '%s\n' 'command|orphan-command' "mktemp|${target}" >"$event_log" assert_fails task5a_capture_event_log_require "$event_log" "$target" created printf '%s\n' \ 'created|/tmp/k3slr-capture.R6ORD001|611:711' \ 'records-removed|/tmp/k3slr-capture.R6ORD001|611:711' \ 'removed|/tmp/k3slr-capture.R6ORD001|611:711' \ 'created|/tmp/k3slr-capture.R6ORD002|612:712' \ 'records-removed|/tmp/k3slr-capture.R6ORD002|612:712' \ 'removed|/tmp/k3slr-capture.R6ORD002|612:712' >"$ownership_log" printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6ORD002' \ 'rm|/tmp/k3slr-capture.R6ORD002' \ 'rmdir|/tmp/k3slr-capture.R6ORD002' \ 'mktemp|/tmp/k3slr-capture.R6ORD001' \ 'rm|/tmp/k3slr-capture.R6ORD001' \ 'rmdir|/tmp/k3slr-capture.R6ORD001' >"$event_log" assert_fails task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" '' complete printf '%s\n' \ 'created|/tmp/k3slr-capture.R6PHS001|613:713' \ 'records-removed|/tmp/k3slr-capture.R6PHS001|613:713' \ 'removed|/tmp/k3slr-capture.R6PHS001|613:713' \ "created|${target}|201:301" >"$ownership_log" printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6PHS001' \ 'rm|/tmp/k3slr-capture.R6PHS001' \ 'rmdir|/tmp/k3slr-capture.R6PHS001' \ "mktemp|${target}" \ "rm|${target}" >"$event_log" assert_fails task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" "$target" created printf '%s\n' \ 'created|/tmp/k3slr-capture.R6MAT001|614:714' \ 'records-removed|/tmp/k3slr-capture.R6MAT001|614:714' \ 'removed|/tmp/k3slr-capture.R6MAT001|614:714' >"$ownership_log" printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6MAT001' \ 'rm|/tmp/k3slr-capture.R6MAT001' \ 'rmdir|/tmp/k3slr-capture.R6MAT001' >"$event_log" assert_succeeds task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" '' complete printf '%s\n' \ 'mktemp|/tmp/k3slr-capture.R6MAT001' \ 'command|already-validated' \ 'rm|/tmp/k3slr-capture.R6MAT001' \ 'rmdir|/tmp/k3slr-capture.R6MAT001' >"$event_log" assert_succeeds task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" '' complete ) task5a_corrupt_capture_registration_is_rejected() ( local ownership_log="$1" event_log="$2" description="$3" local created_output='' registration_rc=0 : >"$ownership_log" : >"$event_log" printf '%s\n' 'created|/tmp/k3slr-capture.R5R00001|401:501' >"$ownership_log" printf '%s\n' 'mktemp|/tmp/k3slr-capture.R5R00001' >"$event_log" created_output="$(_k3slr_command /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX)" || registration_rc=$? if [[ -n "$created_output" ]]; then task5a_capture_path_is_safe "$created_output" && [[ -d "$created_output" && ! -L "$created_output" ]] || return 1 task5a_cleanup_capture_probe_path "$created_output" || return 1 fi assert_eq 97 "$registration_rc" \ "$description rejects registration with a foreign incomplete capture" assert_eq '' "$created_output" "$description creates no directory for rejected registration" ) task5a_assert_capture_ownership_complete() { local ownership_log="$1" event_log="$2" path_log="$3" description="$4" local capture_path pinned_identity local -a capture_paths=() ownership_rows=() mapfile -t capture_paths <"$path_log" mapfile -t ownership_rows <"$ownership_log" assert_eq "$(( ${#capture_paths[@]} * 3 ))" "${#ownership_rows[@]}" \ "$description records exactly three ownership phases per capture" for capture_path in "${capture_paths[@]}"; do pinned_identity="$(task5a_capture_ledger_require \ "$ownership_log" "$capture_path" removed)" || return 1 [[ "$pinned_identity" =~ ^[0-9]+:[0-9]+$ ]] || return 1 task5a_capture_event_log_require "$event_log" "$capture_path" removed || return 1 done task5a_capture_ownership_log_require "$ownership_log" '' complete || return 1 task5a_capture_event_log_require "$event_log" '' complete || return 1 } task5a_exact_record_and_pure_parser_contract() ( local value state total free used phase marker_hash package_output package_error package_rc local valid_hash=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef local valid_metadata valid_metadata_hex valid_marker valid_marker_hex invalid_hex expected local context_case='' package_case='' invalid_rc=0 local event_log="${fixture_root}/task5a-record-events.log" local path_log="${fixture_root}/task5a-record-paths.log" local ownership_log="${fixture_root}/task5a-record-ownership.log" : >"$event_log" : >"$path_log" : >"$ownership_log" task5a_record_argv_equals() { local expected_count="${1-}" argument_index local -a compared_arguments=() shift || return 1 [[ "$expected_count" =~ ^[1-9][0-9]*$ ]] || return 1 compared_arguments=("$@") (( ${#compared_arguments[@]} == 2 * expected_count )) || return 1 for (( argument_index=0; argument_index>"$path_log" printf '%s\n' "$created" return 0 ;; /usr/bin/rm) task5a_remove_owned_capture_records "$ownership_log" "$event_log" "$@" return ;; /usr/bin/rmdir) task5a_remove_owned_capture_directory "$ownership_log" "$event_log" "$@" return ;; /usr/local/bin/k3s) task5a_record_argv_equals 4 /usr/local/bin/k3s kubectl config \ current-context "$@" || return 97 command_id=current-context ;; /usr/bin/dpkg-query) if task5a_record_argv_equals 4 /usr/bin/dpkg-query --show \ '--showformat=${Status}|${Version}\n' keepassxc "$@"; then command_id=package-keepassxc elif task5a_record_argv_equals 4 /usr/bin/dpkg-query --show \ '--showformat=${Status}|${Version}\n' cryptsetup-bin "$@"; then command_id=package-cryptsetup-bin else return 97 fi ;; *) return 97 ;; esac printf 'command|%s\n' "$command_id" >>"$event_log" || return 97 case "$command_id" in current-context) case "$context_case" in success) printf 'default\n' ;; missing_lf) printf default ;; stderr) printf 'default\n'; printf 'warning\n' >&2 ;; status) printf 'default\n'; return 1 ;; nul) printf 'default\0\n' ;; *) return 97 ;; esac ;; package-keepassxc|package-cryptsetup-bin) printf '%s' "$package_output" printf '%s' "$package_error" >&2 return "$package_rc" ;; esac } task5a_fixture_ascii_hex() { local input="$1" index character encoded for (( index=0; index<${#input}; index++ )); do character="${input:index:1}" printf -v encoded '%02x' "'$character" printf '%s' "$encoded" done } valid_metadata=$'schema=k3slr-runtime-v1\nluks_uuid=12345678-1234-4abc-8def-1234567890ab\ncontainer_bytes=34359738368\ninner_label=K3S_RECOVERY\nmarker_sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n' valid_metadata_hex="$(task5a_fixture_ascii_hex "$valid_metadata")"$'\n' valid_marker=$'12345678-1234-4abc-8def-1234567890ab\n' valid_marker_hex="$(task5a_fixture_ascii_hex "$valid_marker")"$'\n' assert_succeeds _k3slr_parse_current_context_record $'default\n' value assert_eq default "$value" 'exact current context parses' for value in default $'default\n\n' $'default\r\n' $'other\n' ''; do assert_fails _k3slr_parse_current_context_record "$value" value done assert_succeeds task5a_unowned_capture_cleanup_is_rejected 'record capture fake' assert_succeeds task5a_corrupt_capture_registration_is_rejected \ "$ownership_log" "$event_log" 'record capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" assert_succeeds task5a_cross_ledger_coherence_is_enforced \ "$ownership_log" "$event_log" "$path_log" 'record capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" assert_succeeds task5a_capture_global_parser_contract assert_succeeds task5a_global_corrupt_capture_ledgers_are_rejected \ "$ownership_log" "$event_log" 'record capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" assert_succeeds task5a_capture_cleanup_phase_and_identity_is_enforced \ 'record capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" context_case=success assert_succeeds task5a_command_append_failure_is_rejected \ "$event_log" "$ownership_log" "$event_log" "$path_log" \ 'record fake command-event append failure' \ /usr/local/bin/k3s kubectl config current-context context_case='' : >"$event_log" : >"$path_log" : >"$ownership_log" _k3slr_command /fixture/unknown >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'record fake rejects unknown argv with rc 97' invalid_rc=0 _k3slr_command /usr/local/bin/k3s 'kubectl config' current-context \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'context fake rejects flattened text with wrong argv grouping' invalid_rc=0 _k3slr_command /usr/local/bin/k3s kubectl config current-context unexpected \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'context fake rejects an extra argv element' invalid_rc=0 _k3slr_command /usr/local/bin/k3s kubectl config \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'context fake rejects a missing argv element' invalid_rc=0 _k3slr_command /usr/local/bin/k3s kubectl config get-contexts \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'context fake rejects unknown same-executable argv' invalid_rc=0 _k3slr_command /usr/bin/dpkg-query --show \ '--showformat=${Status}|${Version}\n keepassxc' \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'package fake rejects flattened text with wrong argv grouping' invalid_rc=0 _k3slr_command /usr/bin/dpkg-query --show \ '--showformat=${Status}|${Version}\n' keepassxc unexpected \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'package fake rejects an extra argv element' invalid_rc=0 _k3slr_command /usr/bin/dpkg-query --show \ '--showformat=${Status}|${Version}\n' \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'package fake rejects a missing argv element' invalid_rc=0 _k3slr_command /usr/bin/dpkg-query --list keepassxc \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'package fake rejects unknown same-executable argv' [[ ! -s "$event_log" && ! -s "$path_log" ]] || return 1 context_case=success assert_succeeds _k3slr_current_context value assert_eq default "$value" 'current context query uses exact argv and exact record' context_case=missing_lf assert_fails _k3slr_current_context value context_case=stderr assert_fails _k3slr_current_context value context_case=status assert_fails _k3slr_current_context value context_case=nul assert_fails _k3slr_current_context value assert_succeeds _k3slr_parse_package_query_record state keepassxc \ '2.7.6+dfsg.1-1build3' 0 $'install ok installed|2.7.6+dfsg.1-1build3\n' '' assert_eq installed "$state" 'exact installed package record parses' assert_succeeds _k3slr_parse_package_query_record state cryptsetup-bin \ '2:2.7.0-1ubuntu4.2' 1 '' $'dpkg-query: no packages found matching cryptsetup-bin\n' assert_eq absent "$state" 'exact absent package record parses' assert_succeeds _k3slr_parse_package_query_record state keepassxc \ '2.7.6+dfsg.1-1build3' 0 $'install ok installed|9.9.9\n' '' assert_eq wrong-version "$state" 'wrong installed package version is classified' assert_succeeds _k3slr_parse_package_query_record state keepassxc \ '2.7.6+dfsg.1-1build3' 0 $'deinstall ok config-files|2.7.6+dfsg.1-1build3\n' '' assert_eq malformed "$state" 'wrong dpkg status is malformed' assert_succeeds _k3slr_parse_package_query_record state keepassxc \ '2.7.6+dfsg.1-1build3' 1 'unexpected' $'dpkg-query: no packages found matching keepassxc\n' assert_eq malformed "$state" 'absent record with stdout is malformed' assert_succeeds _k3slr_parse_package_query_record state keepassxc \ '2.7.6+dfsg.1-1build3' 0 $'install ok installed|2.7.6+dfsg.1-1build3\n' $'warning\n' assert_eq malformed "$state" 'installed record with stderr is malformed' assert_succeeds _k3slr_parse_package_query_record state keepassxc \ '2.7.6+dfsg.1-1build3' 2 '' '' assert_eq malformed "$state" 'unexpected dpkg exit is malformed' assert_fails _k3slr_parse_package_query_record state other-package 1.0 0 $'install ok installed|1.0\n' '' package_output=$'install ok installed|2.7.6+dfsg.1-1build3\n' package_error='' package_rc=0 assert_succeeds _k3slr_query_package_state state keepassxc '2.7.6+dfsg.1-1build3' assert_eq installed "$state" 'package query uses exact argv and parses its record' package_output='' package_error=$'dpkg-query: no packages found matching cryptsetup-bin\n' package_rc=1 assert_succeeds _k3slr_query_package_state state cryptsetup-bin '2:2.7.0-1ubuntu4.2' assert_eq absent "$state" 'package query preserves exact absent stderr record' package_output='install ok installed|2.7.6+dfsg.1-1build3' package_error='' package_rc=0 assert_succeeds _k3slr_query_package_state state keepassxc '2.7.6+dfsg.1-1build3' assert_eq malformed "$state" 'package query preserves missing-LF framing for rejection' package_output=$'install ok installed|2.7.6+dfsg.1-1build3\n' package_error=$'warning\n' package_rc=0 assert_succeeds _k3slr_query_package_state state keepassxc '2.7.6+dfsg.1-1build3' assert_eq malformed "$state" 'package query preserves unexpected stderr for rejection' expected="$(printf '%s\n' \ current-context \ current-context \ current-context \ current-context \ current-context \ package-keepassxc \ package-cryptsetup-bin \ package-keepassxc \ package-keepassxc)" assert_succeeds task5a_assert_record_capture_lineage "$expected" \ 'context and package composition' assert_succeeds _k3slr_parse_phase_bytes_record $'phase_bytes=4294967296\n' phase assert_eq 4294967296 "$phase" 'exact estimator record parses' for value in 'phase_bytes=1' $'phase_bytes=01\n' $'phase_bytes=-1\n' \ $'phase_bytes=1\nextra=2\n' $'bytes=1\n' $'phase_bytes=9223372036854775808\n'; do assert_fails _k3slr_parse_phase_bytes_record "$value" phase done assert_succeeds _k3slr_max_phase_bytes phase 7 11 9 assert_eq 11 "$phase" 'phase maximum never decreases after a lower observation' assert_succeeds _k3slr_max_phase_bytes phase 11 11 15 assert_eq 15 "$phase" 'phase maximum advances for a larger third observation' assert_fails _k3slr_max_phase_bytes phase 1 bad 3 assert_succeeds _k3slr_parse_statfs_record $'4096|1000|250\n' total free used assert_eq 4096000 "$total" 'statfs total bytes use fundamental block size' assert_eq 1024000 "$free" 'statfs free bytes use blocks available to user' assert_eq 3072000 "$used" 'statfs conservative used bytes exclude unavailable blocks' for value in '4096|1000|250' $'4096|1000|1001\n' $'0|1|1\n' \ $'4096|01|1\n' $'9223372036854775807|2|1\n' $'4096|1|1\nextra\n'; do assert_fails _k3slr_parse_statfs_record "$value" total free used done assert_fails _k3slr_parse_statfs_record $'1|1|1\n' total total used K3SLR_MINIMUM_FREE_BYTES=10737418240 assert_succeeds _k3slr_conservative_inner_capacity_fits \ 34359738368 2147483648 assert_fails _k3slr_conservative_inner_capacity_fits \ 34359738368 4294967296 assert_fails _k3slr_conservative_inner_capacity_fits 0 1 K3SLR_CONTAINER_SIZE_BYTES=34359738368 K3SLR_INNER_LABEL=K3S_RECOVERY ! declare -F _k3slr_parse_runtime_metadata >/dev/null || \ fail 'raw runtime metadata/UUID shell-variable parser API is forbidden' ! declare -F _k3slr_marker_record_is_valid >/dev/null || \ fail 'raw 37-byte marker shell-variable parser API is forbidden' assert_succeeds _k3slr_parse_runtime_metadata_hex "$valid_metadata_hex" marker_hash assert_eq "$valid_hash" "$marker_hash" 'runtime metadata returns marker hash' assert_succeeds _k3slr_marker_hex_is_valid "$valid_marker_hex" for value in \ "${valid_metadata%$'\n'}" \ "${valid_metadata/luks_uuid=/luks-uuid=}" \ "${valid_metadata/container_bytes=34359738368/container_bytes=34359738369}" \ "${valid_metadata/inner_label=K3S_RECOVERY/inner_label=OTHER}" \ "${valid_metadata/marker_sha256=/marker_sha256=A}" \ $'schema=k3slr-runtime-v1\r\nluks_uuid=12345678-1234-4abc-8def-1234567890ab\ncontainer_bytes=34359738368\ninner_label=K3S_RECOVERY\nmarker_sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n'; do invalid_hex="$(task5a_fixture_ascii_hex "$value")"$'\n' assert_fails _k3slr_parse_runtime_metadata_hex "$invalid_hex" marker_hash done for value in $'12345678-1234-3abc-8def-1234567890ab\n' \ $'12345678-1234-4abc-7def-1234567890ab\n' \ '12345678-1234-4abc-8def-1234567890ab'; do invalid_hex="$(task5a_fixture_ascii_hex "$value")"$'\n' assert_fails _k3slr_marker_hex_is_valid "$invalid_hex" done ) task5a_ntfs_capacity_and_prepackage_seams() ( local value valid_text valid_hex invalid_hex od_record oversized snapshot snapshot_after local total free identity output expected_identity calls=0 task5a_fixture_ascii_hex() { local input="$1" index character encoded for (( index=0; index<${#input}; index++ )); do character="${input:index:1}" printf -v encoded '%02x' "'$character" printf '%s' "$encoded" done } task5a_fixture_prepackage_snapshot() { printf '%s\n' \ 'canonical_partition=/dev/sda3' \ 'partition_major_minor=8:3' \ 'canonical_parent_disk=/dev/sda' \ 'disk_major_minor=8:0' \ "filesystem_uuid=${K3SLR_RECOVERY_FS_UUID}" \ "partuuid=${K3SLR_RECOVERY_PARTUUID}" \ 'filesystem_type=ntfs' \ "model=${K3SLR_RECOVERY_MODEL}" \ "serial=${K3SLR_RECOVERY_SERIAL}" \ "wwn=${K3SLR_RECOVERY_WWN}" \ 'canonical_k3s_partition=/dev/sdb2' \ 'k3s_partition_major_minor=8:34' \ 'canonical_k3s_disk=/dev/sdb' \ 'k3s_disk_major_minor=8:32' \ "k3s_filesystem_uuid=${K3SLR_K3S_FS_UUID}" \ "k3s_partuuid=${K3SLR_K3S_PARTUUID}" \ 'k3s_filesystem_type=ext4' \ "k3s_model=${K3SLR_K3S_MODEL}" \ "k3s_serial=${K3SLR_K3S_SERIAL}" \ "k3s_wwn=${K3SLR_K3S_WWN}" \ 'smart_health=PASSED' \ 'smart_reallocated=0' \ 'smart_pending=0' \ 'smart_uncorrectable=0' \ 'outer_mount=absent' \ 'inner_mount=absent' \ 'source_mounts=absent' \ 'loops=absent' \ 'mapper_directory=physical-root-safe' \ 'mapping_path=absent' \ 'proof_mapping_path=absent' \ 'mapping_names=absent' \ 'ntfs_probe=pass' } od_record=$' 20 30 61 0a\n' assert_succeeds _k3slr_normalize_od_hex_record "$od_record" value assert_eq $'2030610a\n' "$value" 'od tokens normalize to one bounded lowercase hex record' for od_record in $' 20 0A 0a\n' $' 20 gg 0a\n' ' 20 0a' $'\n'; do assert_fails _k3slr_normalize_od_hex_record "$od_record" value done oversized='00 ' for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do oversized+="$oversized" done oversized+='00 ' oversized="${oversized:0:196611}"$'\n' assert_fails _k3slr_normalize_od_hex_record "$oversized" value valid_text=$'Volume Information\n\tCluster Size: 4096\n Volume Size in Clusters: 1000000\nAllocated clusters 250000 (25.0%)\nFree Clusters: 750000 (75.0%)\n' valid_hex="$(task5a_fixture_ascii_hex "$valid_text")"$'\n' assert_succeeds _k3slr_parse_unmounted_ntfs_capacity_hex "$valid_hex" total free assert_eq 4096000000 "$total" 'NTFS total bytes are cluster size times volume clusters' assert_eq 3072000000 "$free" 'NTFS free bytes are cluster size times free clusters' output="$(_k3slr_parse_unmounted_ntfs_capacity_hex "$valid_hex" total free)" || return 1 assert_eq '' "$output" 'NTFS parser never logs normalized or decoded evidence' for value in \ "${valid_text%$'\n'}" \ "${valid_text/Cluster Size: 4096/Cluster Size: 04096}" \ $'Volume Information\n\tCluster\tSize: 4096\nVolume Size in Clusters: 1000000\nAllocated clusters 250000 (25.0%)\nFree Clusters: 750000 (75.0%)\n' \ $'Volume Information\n\tCluster Size:\t4096\nVolume Size in Clusters: 1000000\nAllocated clusters 250000 (25.0%)\nFree Clusters: 750000 (75.0%)\n' \ "${valid_text/Allocated clusters 250000/Allocated clusters 250001}" \ "${valid_text/25.0%/25.1%}" \ "${valid_text/Free Clusters:/Free clusters:}" \ "${valid_text/Free Clusters: 750000 (75.0%)/Free Clusters: 750000 (75.0%) suffix}" \ "${valid_text}Free Clusters: 750000 (75.0%)" \ "${valid_text}Cluster Size: 4096 suffix"$'\n' \ $'Cluster Size: 9223372036854775807\nVolume Size in Clusters: 2\nAllocated clusters 1 (50.0%)\nFree Clusters: 1 (50.0%)\n'; do invalid_hex="$(task5a_fixture_ascii_hex "$value")"$'\n' assert_fails _k3slr_parse_unmounted_ntfs_capacity_hex "$invalid_hex" total free done invalid_hex="${valid_hex%$'\n'}" invalid_hex="${invalid_hex:0:12}00${invalid_hex:12}"$'\n' assert_fails _k3slr_parse_unmounted_ntfs_capacity_hex "$invalid_hex" total free invalid_hex="${valid_hex%$'\n'}" invalid_hex="${invalid_hex:0:12}0d${invalid_hex:12}"$'\n' assert_fails _k3slr_parse_unmounted_ntfs_capacity_hex "$invalid_hex" total free invalid_hex="${valid_hex^^}" assert_fails _k3slr_parse_unmounted_ntfs_capacity_hex "$invalid_hex" total free assert_fails _k3slr_parse_unmounted_ntfs_capacity_hex "${valid_hex%?}f" total free snapshot="$(task5a_fixture_prepackage_snapshot)"$'\n' assert_succeeds _k3slr_validate_prepackage_snapshot "$snapshot" assert_fails _k3slr_parse_prepackage_snapshot_fields "$snapshot" 'bad-name' assert_fails _k3slr_parse_prepackage_snapshot_fields "$snapshot" _k3slr_snapshot_alias for snapshot_after in \ "${snapshot/smart_pending=0/smart_pending=1}" \ "${snapshot/ntfs_probe=pass/ntfs_probe=fail}" \ "${snapshot/loops=absent/loops=present}" \ "${snapshot/disk_major_minor=8:0/disk_major_minor=8:32}" \ "${snapshot/filesystem_uuid=${K3SLR_RECOVERY_FS_UUID}/filesystem_uuid=AAAAAAAAAAAAAAAA}" \ "${snapshot}mapping_names=absent"; do assert_fails _k3slr_validate_prepackage_snapshot "$snapshot_after" done _k3slr_collect_prepackage_snapshot() { local destination_name="$1" selected="$snapshot" calls=$((calls + 1)) if (( calls == 2 )) && [[ -n "${TASK5A_SECOND_SNAPSHOT:-}" ]]; then selected="$TASK5A_SECOND_SNAPSHOT" fi printf -v "$destination_name" '%s' "$selected" } _k3slr_capture_ntfsinfo_hex() { local destination_name="$1" partition="$2" [[ "$partition" == /dev/sda3 ]] || return 1 printf -v "$destination_name" '%s' "$valid_hex" } calls=0 TASK5A_SECOND_SNAPSHOT='' assert_succeeds _k3slr_prepackage_device_preflight assert_eq 2 "$calls" 'pre-package device preflight pins byte-equal before/after snapshots' calls=0 TASK5A_SECOND_SNAPSHOT="${snapshot/serial=${K3SLR_RECOVERY_SERIAL}/serial=DRIFTED}" assert_fails _k3slr_prepackage_device_preflight assert_eq 2 "$calls" 'pre-package drift is observed before refusal' calls=0 TASK5A_SECOND_SNAPSHOT='' assert_succeeds _k3slr_collect_unmounted_ntfs_capacity value identity assert_eq "$valid_hex" "$value" 'capacity collector returns only normalized hex' expected_identity="/dev/sda3|8:3|/dev/sda|8:0|${K3SLR_RECOVERY_FS_UUID}|${K3SLR_RECOVERY_PARTUUID}|ntfs|${K3SLR_RECOVERY_MODEL}|${K3SLR_RECOVERY_SERIAL}|${K3SLR_RECOVERY_WWN}|8:32" assert_eq "$expected_identity" "$identity" 'capacity collector returns the strict pinned identity tuple' assert_eq 2 "$calls" 'capacity collector repeats the full snapshot around ntfsinfo' calls=0 TASK5A_SECOND_SNAPSHOT="${snapshot/partition_major_minor=8:3/partition_major_minor=8:4}" value=unchanged identity=unchanged assert_fails _k3slr_collect_unmounted_ntfs_capacity value identity assert_eq unchanged "$value" 'drift failure does not publish normalized hex' assert_eq unchanged "$identity" 'drift failure does not publish identity' ) task5a_exact_process_capture_contract() ( local capture_case=success stdout stderr status invalid_rc=0 expected local event_log="${fixture_root}/task5a-capture-events.log" local path_log="${fixture_root}/task5a-capture-paths.log" local ownership_log="${fixture_root}/task5a-capture-ownership.log" : >"$event_log" : >"$path_log" : >"$ownership_log" task5a_capture_argv_equals() { local expected_count="${1-}" argument_index local -a compared_arguments=() shift || return 1 [[ "$expected_count" =~ ^[1-9][0-9]*$ ]] || return 1 compared_arguments=("$@") (( ${#compared_arguments[@]} == 2 * expected_count )) || return 1 for (( argument_index=0; argument_index>"$path_log" printf '%s\n' "$created" return 0 ;; /usr/bin/rm) task5a_remove_owned_capture_records "$ownership_log" "$event_log" "$@" return ;; /usr/bin/rmdir) task5a_remove_owned_capture_directory "$ownership_log" "$event_log" "$@" return ;; /fixture/capture) task5a_capture_argv_equals 3 /fixture/capture --mode emit "$@" || return 97 printf 'command|fixture-capture\n' >>"$event_log" || return 97 case "$capture_case" in success) printf 'stdout line\n' printf 'stderr line\n' >&2 return 7 ;; nul) printf 'unsafe\0record\n' ;; *) return 97 ;; esac ;; *) return 97 ;; esac } assert_succeeds task5a_unowned_capture_cleanup_is_rejected \ 'generic process capture fake' assert_succeeds task5a_capture_cleanup_phase_and_identity_is_enforced \ 'generic process capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" assert_succeeds task5a_cross_ledger_coherence_is_enforced \ "$ownership_log" "$event_log" "$path_log" 'generic process capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" capture_case=success assert_succeeds task5a_command_append_failure_is_rejected \ "$event_log" "$ownership_log" "$event_log" "$path_log" \ 'generic fake command-event append failure' \ /fixture/capture --mode emit : >"$event_log" : >"$path_log" : >"$ownership_log" _k3slr_command /fixture/unknown >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'process-capture fake rejects unknown argv with rc 97' invalid_rc=0 _k3slr_command /fixture/capture '--mode emit' \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'process-capture fake rejects flattened text with wrong argv grouping' invalid_rc=0 _k3slr_command /fixture/capture --mode emit unexpected \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'process-capture fake rejects the known executable with an extra argv element' invalid_rc=0 _k3slr_command /fixture/capture --mode >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'process-capture fake rejects the known executable with a missing argv element' invalid_rc=0 _k3slr_command /fixture/capture --mode discard \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'process-capture fake rejects unknown same-executable argv' [[ ! -s "$event_log" && ! -s "$path_log" ]] || return 1 assert_succeeds _k3slr_capture_process stdout stderr status \ /fixture/capture --mode emit assert_eq $'stdout line\n' "$stdout" 'process capture preserves stdout terminal LF' assert_eq $'stderr line\n' "$stderr" 'process capture preserves stderr terminal LF' assert_eq 7 "$status" 'process capture preserves nonzero exit status' capture_case=nul assert_fails _k3slr_capture_process stdout stderr status \ /fixture/capture --mode emit expected="$(printf '%s\n' fixture-capture fixture-capture)" assert_succeeds task5a_assert_process_capture_lineage "$expected" \ 'success and NUL-rejection process capture' assert_fails _k3slr_capture_process stdout stdout status \ /fixture/capture --mode emit assert_succeeds task5a_assert_process_capture_lineage "$expected" \ 'duplicate-destination rejection leaves no capture residue' ) task5a_prepackage_system_command_matrix() ( local log="${fixture_root}/task5a-prepackage-system.log" local event_log="${fixture_root}/task5a-prepackage-system-events.log" local path_log="${fixture_root}/task5a-prepackage-system-paths.log" local ownership_log="${fixture_root}/task5a-prepackage-system-ownership.log" local cryptsetup_log="${fixture_root}/task5a-prepackage-system-cryptsetup.log" local snapshot system_case expected unknown_rc=0 wrong_group_rc=0 extra_arg_rc=0 missing_arg_rc=0 : >"$log" : >"$event_log" : >"$path_log" : >"$ownership_log" : >"$cryptsetup_log" task5a_argv_equals() { local expected_count="${1-}" argument_index local -a compared_arguments=() shift || return 1 [[ "$expected_count" =~ ^[1-9][0-9]*$ ]] || return 1 compared_arguments=("$@") (( ${#compared_arguments[@]} == 2 * expected_count )) || return 1 for (( argument_index=0; argument_index>"$cryptsetup_log" ;; esac done case "${1-}" in /usr/bin/mktemp) (( $# == 4 )) && [[ "$2" == --directory && "$3" == --tmpdir=/tmp && "$4" == k3slr-capture.XXXXXXXX ]] || return 97 task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" '' complete || return 97 created="$("$@")" || return 1 [[ "$created" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8}$ && -d "$created" && ! -L "$created" ]] || return 97 if ! task5a_capture_ledger_register \ "$ownership_log" "$event_log" "$created"; then task5a_cleanup_capture_probe_path "$created" || return 1 return 97 fi printf '%s\n' "$created" >>"$path_log" printf '%s\n' "$created" return 0 ;; /usr/bin/rm) task5a_remove_owned_capture_records "$ownership_log" "$event_log" "$@" return ;; /usr/bin/rmdir) task5a_remove_owned_capture_directory "$ownership_log" "$event_log" "$@" return ;; esac case "${1-}" in /usr/bin/readlink) if task5a_argv_equals 4 \ /usr/bin/readlink -f -- "$K3SLR_RECOVERY_PARTITION_BY_ID" "$@"; then command_id=readlink-recovery-partition elif task5a_argv_equals 4 \ /usr/bin/readlink -f -- "$K3SLR_RECOVERY_DISK_BY_ID" "$@"; then command_id=readlink-recovery-disk elif task5a_argv_equals 4 \ /usr/bin/readlink -f -- "$K3SLR_K3S_PARTITION_BY_ID" "$@"; then command_id=readlink-k3s-partition elif task5a_argv_equals 4 \ /usr/bin/readlink -f -- "$K3SLR_K3S_DISK_BY_ID" "$@"; then command_id=readlink-k3s-disk else : fi ;; /usr/bin/lsblk) if task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output PKNAME -- /dev/sda3 "$@"; then command_id=lsblk-recovery-partition-parent elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- /dev/sda3 "$@"; then command_id=lsblk-recovery-partition-majmin elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output MODEL -- /dev/sda "$@"; then command_id=lsblk-recovery-model elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output SERIAL -- /dev/sda "$@"; then command_id=lsblk-recovery-serial elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output WWN -- /dev/sda "$@"; then command_id=lsblk-recovery-wwn elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- /dev/sda "$@"; then command_id=lsblk-recovery-disk-majmin elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output PKNAME -- /dev/sdb2 "$@"; then command_id=lsblk-k3s-partition-parent elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- /dev/sdb2 "$@"; then command_id=lsblk-k3s-partition-majmin elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output MODEL -- /dev/sdb "$@"; then command_id=lsblk-k3s-model elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output SERIAL -- /dev/sdb "$@"; then command_id=lsblk-k3s-serial elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output WWN -- /dev/sdb "$@"; then command_id=lsblk-k3s-wwn elif task5a_argv_equals 7 \ /usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- /dev/sdb "$@"; then command_id=lsblk-k3s-disk-majmin elif task5a_argv_equals 5 \ /usr/bin/lsblk --noheadings --raw --output NAME,TYPE "$@"; then command_id=lsblk-all-names-types else : fi ;; /usr/bin/sudo) if task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value \ --match-tag UUID -- /dev/sda3 "$@"; then command_id=blkid-recovery-uuid elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value \ --match-tag PARTUUID -- /dev/sda3 "$@"; then command_id=blkid-recovery-partuuid elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value \ --match-tag TYPE -- /dev/sda3 "$@"; then command_id=blkid-recovery-type elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value \ --match-tag UUID -- /dev/sdb2 "$@"; then command_id=blkid-k3s-uuid elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value \ --match-tag PARTUUID -- /dev/sdb2 "$@"; then command_id=blkid-k3s-partuuid elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value \ --match-tag TYPE -- /dev/sdb2 "$@"; then command_id=blkid-k3s-type elif task5a_argv_equals 7 \ /usr/bin/sudo --non-interactive -- /usr/sbin/smartctl -H -A /dev/sda "$@"; then command_id=smartctl-recovery-health-attributes elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/bin/findmnt --noheadings --raw \ --output SOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN --mountpoint \ /mnt/k3s-recovery-ssd "$@"; then command_id=findmnt-outer-mountpoint elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/bin/findmnt --noheadings --raw \ --output SOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN --mountpoint \ /srv/recovery/k3s "$@"; then command_id=findmnt-inner-mountpoint elif task5a_argv_equals 10 \ /usr/bin/sudo --non-interactive -- /usr/bin/findmnt --noheadings --raw \ --output TARGET --source /dev/sda3 "$@"; then command_id=findmnt-recovery-source elif task5a_argv_equals 9 \ /usr/bin/sudo --non-interactive -- /usr/sbin/losetup --list \ --noheadings --raw --output \ NAME,BACK-FILE,BACK-INO,BACK-MAJ:MIN,MAJ:MIN,OFFSET,SIZELIMIT "$@"; then command_id=losetup-all elif task5a_argv_equals 6 \ /usr/bin/sudo --non-interactive -- /usr/bin/ntfs-3g.probe \ --readwrite /dev/sda3 "$@"; then command_id=ntfs-probe-readwrite else : fi ;; /usr/bin/stat) if task5a_argv_equals 4 /usr/bin/stat '--format=%F|%u|%g|%a' -- \ /dev/mapper "$@"; then command_id=stat-mapper-directory fi ;; /usr/bin/test) if task5a_argv_equals 4 /usr/bin/test ! -L /dev/mapper "$@"; then command_id=test-mapper-directory-not-symlink elif task5a_argv_equals 4 \ /usr/bin/test ! -e /dev/mapper/k3s-recovery "$@"; then command_id=test-main-mapping-absent elif task5a_argv_equals 4 \ /usr/bin/test ! -L /dev/mapper/k3s-recovery "$@"; then command_id=test-main-mapping-not-symlink elif task5a_argv_equals 4 \ /usr/bin/test ! -e /dev/mapper/k3s-recovery-proof "$@"; then command_id=test-proof-mapping-absent elif task5a_argv_equals 4 \ /usr/bin/test ! -L /dev/mapper/k3s-recovery-proof "$@"; then command_id=test-proof-mapping-not-symlink else : fi ;; *) : ;; esac printf '%s\n' "$command_id" >>"$log" || return 97 printf 'command|%s\n' "$command_id" >>"$event_log" || return 97 [[ "$command_id" != rejected-command ]] || return 97 case "$command_id" in readlink-recovery-partition) case "${system_case:-}" in capture_stdout_no_lf) printf '/dev/sda3' ;; capture_stderr) printf '/dev/sda3\n'; printf 'unexpected stderr\n' >&2 ;; capture_status) printf '/dev/sda3\n'; return 7 ;; *) printf '/dev/sda3\n' ;; esac ;; readlink-recovery-disk) if [[ "${system_case:-}" == wrong_identity ]]; then printf '/dev/sdz\n'; else printf '/dev/sda\n'; fi ;; readlink-k3s-partition) printf '/dev/sdb2\n' ;; readlink-k3s-disk) printf '/dev/sdb\n' ;; lsblk-recovery-partition-parent) printf '/dev/sda\n' ;; lsblk-recovery-partition-majmin) printf '8:3\n' ;; lsblk-recovery-model) printf '%s\n' "$K3SLR_RECOVERY_MODEL" ;; lsblk-recovery-serial) printf '%s\n' "$K3SLR_RECOVERY_SERIAL" ;; lsblk-recovery-wwn) printf '%s\n' "$K3SLR_RECOVERY_WWN" ;; lsblk-recovery-disk-majmin) printf '8:0\n' ;; lsblk-k3s-partition-parent) printf '/dev/sdb\n' ;; lsblk-k3s-partition-majmin) printf '8:34\n' ;; lsblk-k3s-model) printf '%s\n' "$K3SLR_K3S_MODEL" ;; lsblk-k3s-serial) printf '%s\n' "$K3SLR_K3S_SERIAL" ;; lsblk-k3s-wwn) printf '%s\n' "$K3SLR_K3S_WWN" ;; lsblk-k3s-disk-majmin) printf '8:32\n' ;; blkid-recovery-uuid) printf '%s\n' "$K3SLR_RECOVERY_FS_UUID" ;; blkid-recovery-partuuid) printf '%s\n' "$K3SLR_RECOVERY_PARTUUID" ;; blkid-recovery-type) printf 'ntfs\n' ;; blkid-k3s-uuid) printf '%s\n' "$K3SLR_K3S_FS_UUID" ;; blkid-k3s-partuuid) printf '%s\n' "$K3SLR_K3S_PARTUUID" ;; blkid-k3s-type) printf 'ext4\n' ;; smartctl-recovery-health-attributes) if [[ "${system_case:-}" == smart ]]; then printf 'SMART overall-health self-assessment test result: PASSED\n 5 Reallocated_Sector_Ct 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1\n197 Current_Pending_Sector 0\n198 Offline_Uncorrectable 0\n' else printf 'SMART overall-health self-assessment test result: PASSED\n 5 Reallocated_Sector_Ct 0\n197 Current_Pending_Sector 0\n198 Offline_Uncorrectable 0\n' fi ;; findmnt-outer-mountpoint) if [[ "${system_case:-}" == mounted ]]; then printf '/dev/sda3 ntfs3 rw 1 8:3\n'; else return 1; fi ;; findmnt-inner-mountpoint|findmnt-recovery-source) return 1 ;; losetup-all) if [[ "${system_case:-}" == loop ]]; then printf '/dev/loop7 /mnt/file 42 8:3 7:7 0 0\n'; fi ;; stat-mapper-directory) printf 'directory|0|0|755\n' ;; test-mapper-directory-not-symlink) if [[ "${system_case:-}" == mapper_dir_symlink ]]; then return 1; fi ;; test-main-mapping-absent|test-main-mapping-not-symlink|\ test-proof-mapping-absent|test-proof-mapping-not-symlink) ;; lsblk-all-names-types) if [[ "${system_case:-}" == mapping ]]; then printf 'sda disk\nk3s-recovery crypt\n'; else printf 'sda disk\nsda3 part\nsdb disk\nsdb2 part\n'; fi ;; ntfs-probe-readwrite) if [[ "${system_case:-}" == probe ]]; then return 1; fi ;; esac return 0 } assert_succeeds task5a_unowned_capture_cleanup_is_rejected \ 'pre-package system capture fake' assert_succeeds task5a_capture_cleanup_phase_and_identity_is_enforced \ 'pre-package system capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" assert_succeeds task5a_cross_ledger_coherence_is_enforced \ "$ownership_log" "$event_log" "$path_log" 'pre-package system capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" : >"$log" system_case='' assert_succeeds task5a_command_append_failure_is_rejected \ "$log" "$ownership_log" "$event_log" "$path_log" \ 'pre-package fake command-log append failure' \ /usr/bin/readlink -f -- "$K3SLR_RECOVERY_PARTITION_BY_ID" : >"$event_log" : >"$path_log" : >"$ownership_log" : >"$log" assert_succeeds task5a_command_append_failure_is_rejected \ "$event_log" "$ownership_log" "$event_log" "$path_log" \ 'pre-package fake command-event append failure' \ /usr/bin/readlink -f -- "$K3SLR_RECOVERY_PARTITION_BY_ID" : >"$event_log" : >"$path_log" : >"$ownership_log" : >"$log" _k3slr_command /fixture/unknown >/dev/null 2>&1 || unknown_rc=$? assert_eq 97 "$unknown_rc" 'pre-package fake rejects unknown argv with rc 97' _k3slr_command /usr/bin/readlink \ "-f -- ${K3SLR_RECOVERY_PARTITION_BY_ID}" >/dev/null 2>&1 || wrong_group_rc=$? assert_eq 97 "$wrong_group_rc" \ 'pre-package fake rejects same executable and flattened text with wrong argv grouping' _k3slr_command /usr/bin/readlink -f -- \ "$K3SLR_RECOVERY_PARTITION_BY_ID" unexpected >/dev/null 2>&1 || extra_arg_rc=$? assert_eq 97 "$extra_arg_rc" \ 'pre-package fake rejects same executable with an extra argv element' _k3slr_command /usr/bin/readlink -f -- >/dev/null 2>&1 || missing_arg_rc=$? assert_eq 97 "$missing_arg_rc" \ 'pre-package fake rejects same executable with a missing argv element' : >"$log" : >"$event_log" : >"$path_log" : >"$ownership_log" : >"$cryptsetup_log" system_case='' assert_succeeds _k3slr_collect_prepackage_snapshot snapshot assert_succeeds _k3slr_validate_prepackage_snapshot "$snapshot" expected="$(printf '%s\n' \ readlink-recovery-partition \ readlink-recovery-disk \ readlink-k3s-partition \ readlink-k3s-disk \ lsblk-recovery-partition-parent \ lsblk-recovery-partition-majmin \ lsblk-recovery-model \ lsblk-recovery-serial \ lsblk-recovery-wwn \ lsblk-recovery-disk-majmin \ lsblk-k3s-partition-parent \ lsblk-k3s-partition-majmin \ lsblk-k3s-model \ lsblk-k3s-serial \ lsblk-k3s-wwn \ lsblk-k3s-disk-majmin \ blkid-recovery-uuid \ blkid-recovery-partuuid \ blkid-recovery-type \ blkid-k3s-uuid \ blkid-k3s-partuuid \ blkid-k3s-type \ smartctl-recovery-health-attributes \ findmnt-outer-mountpoint \ findmnt-inner-mountpoint \ findmnt-recovery-source \ losetup-all \ stat-mapper-directory \ test-mapper-directory-not-symlink \ test-main-mapping-absent \ test-main-mapping-not-symlink \ test-proof-mapping-absent \ test-proof-mapping-not-symlink \ lsblk-all-names-types \ ntfs-probe-readwrite)" assert_eq "$expected" "$(<"$log")" \ 'pre-package collector uses the independently hard-coded canonical command order' assert_succeeds task5a_assert_capture_lineage_and_cleanup "$expected" \ 'successful pre-package composition' [[ ! -s "$cryptsetup_log" ]] || return 1 for system_case in capture_stdout_no_lf capture_stderr capture_status \ wrong_identity smart mounted loop mapping mapper_dir_symlink probe; do : >"$log" : >"$event_log" : >"$path_log" : >"$ownership_log" : >"$cryptsetup_log" assert_fails _k3slr_collect_prepackage_snapshot snapshot assert_succeeds task5a_assert_capture_lineage_and_cleanup "$(<"$log")" \ "failing pre-package composition (${system_case})" [[ ! -s "$cryptsetup_log" ]] || return 1 done ) task5a_ntfsinfo_pipeline_contract() ( local hex expected_hex pipeline_case='' invalid_rc=0 expected_mawk local event_log="${fixture_root}/task5a-ntfs-pipeline-events.log" local path_log="${fixture_root}/task5a-ntfs-pipeline-paths.log" local ownership_log="${fixture_root}/task5a-ntfs-pipeline-ownership.log" local stage_log="${fixture_root}/task5a-ntfs-pipeline-stages.log" local sudo_status_log="${fixture_root}/task5a-ntfs-pipeline-sudo-status.log" local od_status_log="${fixture_root}/task5a-ntfs-pipeline-od-status.log" local mawk_status_log="${fixture_root}/task5a-ntfs-pipeline-mawk-status.log" task5a_ntfs_argv_equals() { local expected_count="${1-}" argument_index local -a compared_arguments=() shift || return 1 [[ "$expected_count" =~ ^[1-9][0-9]*$ ]] || return 1 compared_arguments=("$@") (( ${#compared_arguments[@]} == 2 * expected_count )) || return 1 for (( argument_index=0; argument_index"$stage_log" : >"$sudo_status_log" : >"$od_status_log" : >"$mawk_status_log" } task5a_assert_ntfs_stage_run() { local description="$1" expected_sudo="$2" expected_od="$3" expected_mawk_status="$4" local stage sudo_count=0 od_count=0 mawk_count=0 local -a stages=() sudo_statuses=() od_statuses=() mawk_statuses=() mapfile -t stages <"$stage_log" mapfile -t sudo_statuses <"$sudo_status_log" mapfile -t od_statuses <"$od_status_log" mapfile -t mawk_statuses <"$mawk_status_log" for stage in "${stages[@]}"; do case "$stage" in sudo) sudo_count=$((sudo_count + 1)) ;; od) od_count=$((od_count + 1)) ;; mawk) mawk_count=$((mawk_count + 1)) ;; *) return 1 ;; esac done assert_eq 3 "${#stages[@]}" "$description runs exactly three pipeline stages" assert_eq 1 "$sudo_count" "$description runs the producer exactly once" assert_eq 1 "$od_count" "$description runs od exactly once" assert_eq 1 "$mawk_count" "$description runs mawk exactly once" assert_eq 1 "${#sudo_statuses[@]}" "$description records one producer status" assert_eq 1 "${#od_statuses[@]}" "$description records one od status" assert_eq 1 "${#mawk_statuses[@]}" "$description records one mawk status" assert_eq "$expected_sudo" "${sudo_statuses[0]}" "$description preserves producer status" assert_eq "$expected_od" "${od_statuses[0]}" "$description preserves od status" assert_eq "$expected_mawk_status" "${mawk_statuses[0]}" \ "$description preserves mawk status" } task5a_assert_ntfs_capture_lineage() { local expected_count="$1" description="$2" local event_index capture_path local -a events=() capture_paths=() mapfile -t events <"$event_log" mapfile -t capture_paths <"$path_log" assert_eq "$expected_count" "${#capture_paths[@]}" \ "$description creates one capture directory per pipeline" assert_eq "$((expected_count * 3))" "${#events[@]}" \ "$description records complete capture cleanup groups" for (( event_index=0; event_index 65536) {\n bad = 1\n exit\n }\n hex = hex $i\n }\n}\nEND {\n if (bad || bytes == 0) exit 1\n print hex\n}' : >"$event_log" : >"$path_log" : >"$ownership_log" task5a_reset_ntfs_stage_logs _k3slr_command() { local created capture_directory command_rc=0 case "${1-}" in /usr/bin/mktemp) task5a_ntfs_argv_equals 4 /usr/bin/mktemp --directory --tmpdir=/tmp \ k3slr-capture.XXXXXXXX "$@" || return 97 task5a_capture_ledgers_require_coherent \ "$ownership_log" "$event_log" '' complete || return 97 created="$("$@")" || return 1 [[ "$created" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8}$ && -d "$created" && ! -L "$created" ]] || return 97 if ! task5a_capture_ledger_register \ "$ownership_log" "$event_log" "$created"; then task5a_cleanup_capture_probe_path "$created" || return 1 return 97 fi printf '%s\n' "$created" >>"$path_log" printf '%s\n' "$created" return 0 ;; /usr/bin/rm) task5a_remove_owned_capture_records "$ownership_log" "$event_log" "$@" return ;; /usr/bin/rmdir) task5a_remove_owned_capture_directory "$ownership_log" "$event_log" "$@" return ;; /usr/bin/sudo) task5a_ntfs_argv_equals 7 /usr/bin/sudo --non-interactive -- \ /usr/bin/ntfsinfo --mft --notime /dev/sda3 "$@" || return 97 printf 'sudo\n' >>"$stage_log" case "$pipeline_case" in boundary_65536) if /usr/bin/head --bytes=65536 /dev/zero; then command_rc=0; else command_rc=$?; fi ;; boundary_65537) if /usr/bin/head --bytes=65537 /dev/zero; then command_rc=0; else command_rc=$?; fi ;; *) printf 'Cluster Size: 4096\nVolume Size in Clusters: 1000000\nAllocated clusters 250000 (25.0%%)\nFree Clusters: 750000 (75.0%%)\n' command_rc=0 ;; esac if [[ "$pipeline_case" == producer_stderr ]]; then printf 'unexpected stderr\n' >&2 elif [[ "$pipeline_case" == producer_failure ]]; then command_rc=7 fi printf '%s\n' "$command_rc" >>"$sudo_status_log" return "$command_rc" ;; /usr/bin/od) task5a_ntfs_argv_equals 4 /usr/bin/od -An -v -tx1 "$@" || return 97 printf 'od\n' >>"$stage_log" if "$@"; then command_rc=0; else command_rc=$?; fi [[ "$pipeline_case" != od_failure || "$command_rc" != 0 ]] || command_rc=8 printf '%s\n' "$command_rc" >>"$od_status_log" return "$command_rc" ;; /usr/bin/mawk) task5a_ntfs_argv_equals 2 /usr/bin/mawk "$expected_mawk" "$@" || return 97 printf 'mawk\n' >>"$stage_log" if "$@"; then command_rc=0; else command_rc=$?; fi [[ "$pipeline_case" != mawk_failure || "$command_rc" != 0 ]] || command_rc=9 printf '%s\n' "$command_rc" >>"$mawk_status_log" return "$command_rc" ;; *) return 97 ;; esac } assert_succeeds task5a_unowned_capture_cleanup_is_rejected \ 'ntfsinfo pipeline capture fake' assert_succeeds task5a_capture_cleanup_phase_and_identity_is_enforced \ 'ntfsinfo pipeline capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" assert_succeeds task5a_cross_ledger_coherence_is_enforced \ "$ownership_log" "$event_log" "$path_log" 'ntfsinfo pipeline capture fake' : >"$event_log" : >"$path_log" : >"$ownership_log" _k3slr_command /fixture/unknown >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects unknown argv with rc 97' invalid_rc=0 _k3slr_command /usr/bin/sudo \ '--non-interactive -- /usr/bin/ntfsinfo --mft --notime /dev/sda3' \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" \ 'ntfsinfo fake rejects grouped sudo arguments that flatten to the expected text' invalid_rc=0 _k3slr_command /usr/bin/sudo --non-interactive -- /usr/bin/ntfsinfo --mft \ --notime /dev/sda3 unexpected >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects an extra sudo argv element' invalid_rc=0 _k3slr_command /usr/bin/sudo --non-interactive -- /usr/bin/ntfsinfo --mft \ --notime >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects a missing sudo argv element' invalid_rc=0 _k3slr_command /usr/bin/sudo --version >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects unknown same-sudo argv' invalid_rc=0 _k3slr_command /usr/bin/od '-An -v -tx1' >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects grouped od argv' invalid_rc=0 _k3slr_command /usr/bin/od -An -v -tx1 unexpected \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects an extra od argv element' invalid_rc=0 _k3slr_command /usr/bin/od -An -v >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects a missing od argv element' invalid_rc=0 _k3slr_command /usr/bin/od --version >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects unknown same-od argv' invalid_rc=0 _k3slr_command /usr/bin/mawk "$expected_mawk" unexpected \ >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects an extra mawk argv element' invalid_rc=0 _k3slr_command /usr/bin/mawk >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects a missing mawk argv element' invalid_rc=0 _k3slr_command /usr/bin/mawk '{ print }' >/dev/null 2>&1 || invalid_rc=$? assert_eq 97 "$invalid_rc" 'ntfsinfo fake rejects unknown same-mawk argv' [[ ! -s "$event_log" && ! -s "$path_log" && ! -s "$stage_log" ]] || return 1 pipeline_case='' assert_succeeds _k3slr_capture_ntfsinfo_hex hex /dev/sda3 expected_hex="$(task5a_fixture_ascii_hex $'Cluster Size: 4096\nVolume Size in Clusters: 1000000\nAllocated clusters 250000 (25.0%)\nFree Clusters: 750000 (75.0%)\n')"$'\n' assert_eq "$expected_hex" "$hex" 'ntfsinfo pipeline returns only normalized bounded hex' assert_succeeds task5a_assert_ntfs_stage_run 'successful ntfsinfo pipeline' 0 0 0 for pipeline_case in producer_failure od_failure mawk_failure producer_stderr; do task5a_reset_ntfs_stage_logs assert_fails _k3slr_capture_ntfsinfo_hex hex /dev/sda3 case "$pipeline_case" in producer_failure) assert_succeeds task5a_assert_ntfs_stage_run \ 'producer-only failure pipeline' 7 0 0 ;; od_failure) assert_succeeds task5a_assert_ntfs_stage_run \ 'od-only failure pipeline' 0 8 0 ;; mawk_failure) assert_succeeds task5a_assert_ntfs_stage_run \ 'mawk-only failure pipeline' 0 0 9 ;; producer_stderr) assert_succeeds task5a_assert_ntfs_stage_run \ 'stderr-rejection pipeline' 0 0 0 ;; esac done pipeline_case=boundary_65536 task5a_reset_ntfs_stage_logs assert_succeeds _k3slr_capture_ntfsinfo_hex hex /dev/sda3 assert_eq 131073 "${#hex}" 'ntfsinfo pipeline accepts exactly 65,536 bytes plus output LF' [[ "$hex" =~ ^0+$'\n'$ ]] || return 1 assert_succeeds task5a_assert_ntfs_stage_run '65,536-byte pipeline boundary' 0 0 0 pipeline_case=boundary_65537 task5a_reset_ntfs_stage_logs assert_fails _k3slr_capture_ntfsinfo_hex hex /dev/sda3 assert_succeeds task5a_assert_ntfs_stage_run '65,537-byte pipeline rejection' 0 0 1 assert_succeeds task5a_assert_ntfs_capture_lineage 7 \ 'success, status, stderr, and byte-boundary pipelines' ) task5a_dry_run_and_prepackage_action_binding() ( local mode wrapper main_name log output action_rc=0 probe_rc=0 record='' local command_calls=0 for mode in prepare open close; do case "$mode" in prepare) wrapper="$PREPARE_WRAPPER_PATH" ;; open) wrapper="$OPEN_WRAPPER_PATH" ;; close) wrapper="$CLOSE_WRAPPER_PATH" ;; esac main_name="_k3slr_${mode}_main" log="${fixture_root}/task5a-${mode}-dry-run.log" : >"$log" # shellcheck source=/dev/null source "$wrapper" _k3slr_load_contract() { printf 'contract\n' >>"$log" } _k3slr_prepackage_device_preflight() { printf 'prepackage\n' >>"$log" } _k3slr_require_execute_tty() { printf 'unexpected-tty\n' >>"$log" return 97 } _k3slr_command() { (( command_calls += 1 )) printf 'unexpected-command:%s\n' "$*" >>"$log" return 97 } output="$("$main_name")" || return 1 [[ "$output" == "DRY RUN: ${mode} lifecycle; no changes made." ]] || return 1 assert_eq $'contract\nprepackage' "$(<"$log")" \ "$mode dry-run uses only the non-interactive pre-package evidence seam" : >"$log" _k3slr_prepackage_device_preflight() { printf 'prepackage\n' >>"$log" return 1 } assert_fails "$main_name" assert_eq $'contract\nprepackage' "$(<"$log")" \ "$mode dry-run fails closed when cached read-only evidence is unavailable" done log="${fixture_root}/task5a-device-validator-binding.log" : >"$log" _k3slr_prepackage_device_preflight() { printf 'prepackage\n' >>"$log" } _k3slr_command() { (( command_calls += 1 )) return 97 } task5b_legacy_binding_probe_payload() { local binding_case="${1-}" action_rc=0 command_calls=0 (( $# == 1 )) || return 1 case "$binding_case" in prepare-device) _k3slr_lifecycle_action prepare device-validator \ _k3slr_prepare_invocation || action_rc=$? ;; prepare-sudo) _k3slr_lifecycle_action prepare sudo _k3slr_prepare_invocation || action_rc=$? ;; open-device) _k3slr_lifecycle_action open 'device-validator(--expect-device-ready)' || action_rc=$? ;; *) return 1 ;; esac task5b_production_probe_emit_ok legacy_binding "$action_rc" "$command_calls" } task5b_production_probe_adversary_matrix || return 1 task5b_production_probe_static_graph_is_closed legacy_binding || return 1 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm legacy_binding || return 1 task5b_legacy_binding_probe_payload prepare-device )" || probe_rc=$? assert_eq 0 "$probe_rc" 'prepare device-validator binding probe completes safely' assert_eq 'TASK5B_PROBE_OK|legacy_binding|1|0' "$record" \ 'prepare device-validator binding is fail-closed before commands' assert_eq '' "$(<"$log")" \ 'prepare action integration remains fail-closed until its later checkpoint' record='' probe_rc=0 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm legacy_binding || return 1 task5b_legacy_binding_probe_payload prepare-sudo )" || probe_rc=$? assert_eq 0 "$probe_rc" 'prepare sudo binding probe completes safely' assert_eq 'TASK5B_PROBE_OK|legacy_binding|97|1' "$record" \ 'prepare sudo binding reaches only the modeled command failure' record='' probe_rc=0 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm legacy_binding || return 1 task5b_legacy_binding_probe_payload open-device )" || probe_rc=$? assert_eq 0 "$probe_rc" 'open device-validator binding probe completes safely' assert_eq 'TASK5B_PROBE_OK|legacy_binding|1|0' "$record" \ 'open device-validator binding is fail-closed before commands' ) # The production break this catches is a Task 5B fake command boundary that # accepts a hazardous near-match or reaches a real executable. Expected argv # below are independent test literals; none are derived from production arrays. task5b_gate0_route_equals() { local route_id="${1-}" argument_index local -a actual_arguments=() expected_arguments=() shift || return 1 actual_arguments=("$@") case "$route_id" in sudo-validate) expected_arguments=(/usr/bin/sudo --validate) ;; sudo-cached-validate) expected_arguments=(/usr/bin/sudo --non-interactive --validate) ;; apt-update) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/apt-get update) ;; apt-install) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/apt-get install --yes keepassxc=2.7.6+dfsg.1-1build3 cryptsetup-bin=2:2.7.0-1ubuntu4.2) ;; feasibility-execute) expected_arguments=(/usr/bin/bash /home/donghyeon/workspace/platform/scripts/validate/k3s-local-recovery-feasibility.sh --execute) ;; outer-scaffold-mkdir) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mkdir --mode=0700 -- /mnt/k3s-recovery-ssd) ;; outer-mount) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mount -t ntfs3 -o rw,uid=1000,gid=1000,dmask=0077,fmask=0177,nodev,nosuid,noexec /dev/disk/by-id/wwn-0x500a0751e6aa6254-part3 /mnt/k3s-recovery-ssd) ;; layout-root-mkdir) expected_arguments=(/usr/bin/mkdir --mode=0700 -- /mnt/k3s-recovery-ssd/HyeonworksRecovery) ;; layout-vault-mkdir) expected_arguments=(/usr/bin/mkdir --mode=0700 -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault) ;; layout-backups-mkdir) expected_arguments=(/usr/bin/mkdir --mode=0700 -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault/backups) ;; layout-containers-mkdir) expected_arguments=(/usr/bin/mkdir --mode=0700 -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers) ;; probe-mktemp) expected_arguments=(/usr/bin/mktemp --tmpdir=/mnt/k3s-recovery-ssd/HyeonworksRecovery .k3slr-hardlink-probe.XXXXXXXX) ;; probe-link) expected_arguments=(/usr/bin/ln -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1 /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1.link) ;; probe-unlink-link) expected_arguments=(/usr/bin/unlink -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1.link) ;; probe-unlink-source) expected_arguments=(/usr/bin/unlink -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1) ;; probe-sync) expected_arguments=(/usr/bin/sync --file-system /mnt/k3s-recovery-ssd/HyeonworksRecovery) ;; keepass-db-create) expected_arguments=(/usr/bin/keepassxc-cli db-create --quiet --set-password /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault/hyeonworks-recovery.kdbx) ;; keepass-add) expected_arguments=(/usr/bin/keepassxc-cli add --quiet --generate --length 40 --lower --upper --numeric --every-group /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault/hyeonworks-recovery.kdbx 'K3s Recovery LUKS') ;; container-dd) expected_arguments=(/usr/bin/dd if=/dev/zero of=/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks bs=16M count=2048 conv=excl,fsync status=progress) ;; outer-umount) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/umount -- /mnt/k3s-recovery-ssd) ;; loop-create) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/sbin/losetup --find --show --nooverlap -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks) ;; luks-format) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup luksFormat --batch-mode --type luks2 --key-file=- /dev/loop37) ;; luks-open) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup open --type luks2 --key-file=- /dev/loop37 k3s-recovery) ;; mkfs-ext4) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/sbin/mkfs.ext4 -F -L K3S_RECOVERY /dev/mapper/k3s-recovery) ;; inner-parent-mkdir) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mkdir --mode=0700 -- /srv/recovery) ;; inner-mountpoint-mkdir) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mkdir --mode=0700 -- /srv/recovery/k3s) ;; inner-mount) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mount -t ext4 -o rw,nodev,nosuid,noexec /dev/mapper/k3s-recovery /srv/recovery/k3s) ;; inner-chown) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/chown root:root /srv/recovery/k3s) ;; inner-chmod) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/chmod 0700 /srv/recovery/k3s) ;; marker-mktemp) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mktemp --tmpdir=/srv/recovery/k3s .k3slr-marker.tmp.XXXXXXXX) ;; marker-dd) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/dd of=/srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1 bs=37 count=1 iflag=fullblock conv=notrunc,fsync status=none) ;; outer-metadata-mktemp) expected_arguments=(/usr/bin/mktemp --tmpdir=/mnt/k3s-recovery-ssd/HyeonworksRecovery .k3slr-volume.tmp.XXXXXXXX) ;; inner-metadata-mktemp) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/mktemp --tmpdir=/srv/recovery/k3s .k3slr-volume.tmp.XXXXXXXX) ;; outer-metadata-dd) expected_arguments=(/usr/bin/dd of=/mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 bs=4096 count=1 iflag=fullblock conv=notrunc,fsync status=none) ;; inner-metadata-dd) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/dd if=/mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 of=/srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1 bs=4096 count=1 iflag=fullblock conv=notrunc,fsync status=none) ;; marker-stage-sync) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/sync --file-system /srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1) ;; marker-publish) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/ln -- /srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1 /srv/recovery/k3s/.recovery-marker) ;; outer-stage-sync) expected_arguments=(/usr/bin/sync --file-system /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1) ;; inner-stage-sync) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/sync --file-system /srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1) ;; outer-metadata-publish) expected_arguments=(/usr/bin/ln -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 /mnt/k3s-recovery-ssd/HyeonworksRecovery/volume.env) ;; inner-metadata-publish) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/ln -- /srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1 /srv/recovery/k3s/.recovery-volume.env) ;; marker-stage-unlink) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/unlink -- /srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1) ;; outer-stage-unlink) expected_arguments=(/usr/bin/unlink -- /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1) ;; inner-stage-unlink) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/unlink -- /srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1) ;; inner-parent-sync) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/sync --file-system /srv/recovery/k3s) ;; inner-umount) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/bin/umount -- /srv/recovery/k3s) ;; mapping-close) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup close k3s-recovery) ;; loop-detach) expected_arguments=(/usr/bin/sudo --non-interactive -- /usr/sbin/losetup --detach /dev/loop37) ;; *) return 1 ;; esac (( ${#actual_arguments[@]} == ${#expected_arguments[@]} )) || return 1 for (( argument_index=0; argument_index<${#expected_arguments[@]}; argument_index++ )); do [[ "${actual_arguments[argument_index]}" == "${expected_arguments[argument_index]}" ]] || return 1 done } task5b_gate0_fake_command() { local route_id local -a route_ids=( sudo-validate sudo-cached-validate apt-update apt-install feasibility-execute outer-scaffold-mkdir outer-mount layout-root-mkdir layout-vault-mkdir layout-backups-mkdir layout-containers-mkdir probe-mktemp probe-link probe-unlink-link probe-unlink-source probe-sync keepass-db-create keepass-add container-dd outer-umount loop-create luks-format luks-open mkfs-ext4 inner-parent-mkdir inner-mountpoint-mkdir inner-mount inner-chown inner-chmod marker-mktemp marker-dd outer-metadata-mktemp inner-metadata-mktemp outer-metadata-dd inner-metadata-dd marker-stage-sync marker-publish outer-stage-sync inner-stage-sync outer-metadata-publish inner-metadata-publish marker-stage-unlink outer-stage-unlink inner-stage-unlink inner-parent-sync inner-umount mapping-close loop-detach ) for route_id in "${route_ids[@]}"; do if task5b_gate0_route_equals "$route_id" "$@"; then (( TASK5B_GATE0_MODEL_DISPATCH_COUNT += 1 )) (( TASK5B_GATE0_MODEL_EFFECT_COUNT += 1 )) TASK5B_GATE0_LAST_ROUTE="$route_id" TASK5B_GATE0_LAST_OUTPUT="modeled:${route_id}" return 0 fi done return 97 } readonly -a TASK5B_GATE0_ACTIVE_GRAPH_FUNCTIONS=( _k3slr_command assert_eq fail task5b_gate0_active_matrix task5b_gate0_assert_rejected task5b_gate0_assert_route task5b_gate0_fake_command task5b_gate0_reset_route_record task5b_gate0_route_equals task5b_gate0_assignment_only_status task5b_gate0_real_exec_fuse ) task5b_gate0_assignment_only_status() { local candidate="${1-}" lhs rhs character next_character quote='' mode=scalar local index=0 length depth=0 escaped=0 word_ended=0 append_assignment=0 (( $# == 1 )) || return 1 candidate="${candidate#"${candidate%%[![:space:]]*}"}" candidate="${candidate%"${candidate##*[![:space:]]}"}" [[ "$candidate" == *=* ]] || return 1 lhs="${candidate%%=*}" rhs="${candidate#*=}" if [[ "$lhs" == *+ ]]; then lhs="${lhs%+}" append_assignment=1 fi [[ "$lhs" =~ ^[a-zA-Z_][a-zA-Z0-9_]*(\[([a-zA-Z0-9_]+|[a-zA-Z0-9_]+[[:space:]]*[-+][[:space:]]*[0-9]+)\])?$ ]] || return 1 length="${#rhs}" (( length > 0 )) || return 0 (( append_assignment == 0 )) || [[ "${rhs:0:1}" == '(' ]] || return 1 if [[ "${rhs:0:1}" == '(' ]]; then mode=compound depth=1 index=1 fi while (( index < length )); do character="${rhs:index:1}" next_character='' (( index + 1 >= length )) || next_character="${rhs:index + 1:1}" if (( escaped == 1 )); then escaped=0 (( index += 1 )) continue fi case "$quote" in single) [[ "$character" != "'" ]] || quote='' (( index += 1 )) continue ;; double) case "$character" in '\\') escaped=1 ;; '"') quote='' ;; '`') return 1 ;; '$') [[ "$next_character" != '(' ]] || return 1 ;; esac (( index += 1 )) continue ;; esac if (( depth > 0 )) && [[ "$mode" == arithmetic ]]; then case "$character" in '`') return 1 ;; '$') [[ "$next_character" != '(' ]] || return 1 ;; '(') (( depth += 1 )) ;; ')') (( depth -= 1 )) ;; esac (( index += 1 )) continue fi case "$character" in "'") quote=single ;; '"') quote=double ;; '\\') escaped=1 ;; '`'|';'|'&'|'|'|'<'|'>') return 1 ;; '$') if [[ "${rhs:index:3}" == '$((' ]]; then mode=arithmetic depth=2 (( index += 3 )) continue fi [[ "$next_character" != '(' ]] || return 1 ;; '(') [[ "$mode" == compound ]] || return 1 (( depth += 1 )) ;; ')') [[ "$mode" == compound ]] || return 1 (( depth -= 1 )) (( depth >= 0 )) || return 1 if (( depth == 0 )); then (( index += 1 )) while (( index < length )); do [[ "${rhs:index:1}" =~ [[:space:]] ]] || return 1 (( index += 1 )) done return 0 fi ;; $'\n'|$'\r') [[ "$mode" == compound ]] || return 1 ;; [[:space:]]) if [[ "$mode" != compound ]]; then word_ended=1 fi ;; *) (( word_ended == 0 )) || return 1 ;; esac (( index += 1 )) done [[ -z "$quote" && "$escaped" == 0 ]] || return 1 if [[ "$mode" == compound && "$depth" -gt 0 ]]; then return 2 fi (( depth == 0 )) } task5b_gate0_real_exec_fuse() { local candidate="${BASH_COMMAND-}" execution_word='' assignment_rc=0 candidate="${candidate#"${candidate%%[![:space:]]*}"}" while [[ "$candidate" =~ ^(if|elif|while|until|then|\!|time)[[:space:]]+(.+)$ ]]; do candidate="${BASH_REMATCH[2]}" done case "$candidate" in ''|'{'|'}'|'[['*|'(('*|for\ *|case\ *|do|done|esac) return 0 ;; esac if [[ "$candidate" =~ ^[a-zA-Z_][a-zA-Z0-9_]*(\[[^]]+\])?\+?= ]]; then task5b_gate0_assignment_only_status "$candidate" || assignment_rc=$? (( assignment_rc == 0 )) && return 0 (( TASK5B_GATE0_ACTUAL_EXEC_COUNT += 1 )) if [[ "${TASK5B_GATE0_FUSE_PROBE_SILENT:-0}" != 1 ]]; then printf 'Task 5B Gate 0 blocked assignment-prefixed execution\n' >&9 fi exit 96 fi read -r execution_word _ <<<"$candidate" execution_word="${execution_word%;}" case "$execution_word" in \$*|\"\$*|\'\$*|/*|\"/*|\'/*|command|builtin|exec|eval|source|.) (( TASK5B_GATE0_ACTUAL_EXEC_COUNT += 1 )) if [[ "${TASK5B_GATE0_FUSE_PROBE_SILENT:-0}" != 1 ]]; then printf 'Task 5B Gate 0 blocked dynamic/external execution word: %s\n' \ "$execution_word" >&9 fi exit 96 ;; esac case "$execution_word" in _k3slr_command|assert_eq|fail|task5b_gate0_active_matrix|\ task5b_gate0_assert_rejected|task5b_gate0_assert_route|\ task5b_gate0_fake_command|task5b_gate0_reset_route_record|\ task5b_gate0_route_equals|exit|local|shift|return|printf|read|:) return 0 ;; esac (( TASK5B_GATE0_ACTUAL_EXEC_COUNT += 1 )) if [[ "${TASK5B_GATE0_FUSE_PROBE_SILENT:-0}" != 1 ]]; then printf 'Task 5B Gate 0 blocked non-builtin execution word: %s\n' \ "$execution_word" >&9 fi exit 96 } task5b_gate0_adversary_direct() { /fixture/task5b-unlisted-direct } task5b_gate0_adversary_indented() { "$@" } task5b_gate0_adversary_control_word() { if command "$@"; then : fi } task5b_gate0_adversary_array() { local -a task5b_runner=(/fixture/task5b-unlisted-array) "${task5b_runner[@]}" } task5b_gate0_adversary_variable() { local task5b_runner=/fixture/task5b-unlisted-variable "$task5b_runner" } task5b_gate0_adversary_shell_c() { /fixture/unlisted/bash -c ':' } task5b_gate0_adversary_helper_root() { task5b_gate0_adversary_helper_leaf } task5b_gate0_adversary_helper_leaf() { /fixture/task5b-unlisted-helper } task5b_gate0_adversary_trap_disable() { trap - DEBUG set +T shopt -u extdebug } task5b_gate0_adversary_assignment_bare() { TASK5B_ASSIGNMENT_PREFIX= trap - DEBUG } task5b_gate0_adversary_assignment_simple() { TASK5B_ASSIGNMENT_PREFIX=1 /usr/bin/printf TASK5B_ASSIGNMENT_PREFIX_CANARY } task5b_gate0_adversary_assignment_quoted() { TASK5B_ASSIGNMENT_PREFIX="a b" trap - DEBUG } task5b_gate0_adversary_assignment_indexed() { TASK5B_ASSIGNMENT_PREFIX[0]=1 trap - DEBUG } task5b_gate0_static_safe_sample() { local value='' task5b_gate0_route_equals sudo-validate /usr/bin/sudo --validate || return 1 value=modeled [[ "$value" == modeled ]] } task5b_gate0_static_assignment_prefixes_are_closed() { local body="${1-}" line candidate='' compound_candidate='' local assignment_rc=0 (( $# == 1 )) || return 1 while IFS= read -r line; do candidate="${line#"${line%%[![:space:]]*}"}" candidate="${candidate%"${candidate##*[![:space:]]}"}" while [[ "$candidate" =~ ^(if|elif|while|until|then|\!|time)[[:space:]]+(.+)$ ]]; do candidate="${BASH_REMATCH[2]}" done [[ "$candidate" != *';' ]] || candidate="${candidate%;}" if [[ -n "$compound_candidate" ]]; then compound_candidate+=$'\n'"$candidate" assignment_rc=0 task5b_gate0_assignment_only_status "$compound_candidate" || assignment_rc=$? case "$assignment_rc" in 0) compound_candidate='' ;; 2) ;; *) return 1 ;; esac continue fi [[ "$candidate" =~ ^[a-zA-Z_][a-zA-Z0-9_]*(\[[^]]+\])?\+?= ]] || continue assignment_rc=0 task5b_gate0_assignment_only_status "$candidate" || assignment_rc=$? case "$assignment_rc" in 0) ;; 2) compound_candidate="$candidate" ;; *) return 1 ;; esac done <<<"$body" if [[ -n "$compound_candidate" ]]; then return 1 fi } task5b_gate0_static_execution_positions_are_closed() { local body="${1-}" (( $# == 1 )) || return 1 [[ -n "$body" ]] || return 1 if /usr/bin/grep -En \ '^[[:space:]]*((if|elif|while|until|then|!|time)[[:space:]]+)*("?/?[^[:space:]]*/(ba|z|k|da)?sh|bash|sh)[[:space:]].*(-c|--command)([[:space:]]|$)|^[[:space:]]*((if|elif|while|until|then|!|time)[[:space:]]+)*("?\$|\$\{|/|"/|command([[:space:]]|$)|builtin[[:space:]]+command([[:space:]]|$)|exec([[:space:]]|$)|eval([[:space:]]|$)|source([[:space:]]|$)|\.[[:space:]])|\$\*|\$\{[^}]+\[\*\][^}]*\}|`|\$\(|^[[:space:]]*(trap([[:space:]]|$)|set[[:space:]]+\+T([[:space:]]|$)|shopt[[:space:]].*extdebug)' \ <<<"$body" >/dev/null; then return 1 fi } task5b_gate0_static_body_is_closed() { local body="${1-}" (( $# == 1 )) || return 1 task5b_gate0_static_assignment_prefixes_are_closed "$body" || return 1 task5b_gate0_static_execution_positions_are_closed "$body" } task5b_gate0_definition_hash() { local destination_name="${1-}" function_name="${2-}" body='' hash_record='' (( $# == 2 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 body="$(declare -f "$function_name")" || return 1 hash_record="$(printf '%s\n' "$body" | /usr/bin/sha256sum)" || return 1 [[ "$hash_record" =~ ^([0-9a-f]{64})[[:space:]]+-$ ]] || return 1 printf -v "$destination_name" '%s' "${BASH_REMATCH[1]}" } task5b_gate0_normalize_static_body_data_lines() { local body="${1-}" destination_name="${2-}" function_name="${3-}" local line normalized='' previous_continues=0 (( $# == 3 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 while IFS= read -r line; do if (( previous_continues == 1 )); then normalized+=' task5b_static_argument ' elif [[ "$function_name" == task5b_gate0_route_equals && "$line" =~ ^[[:space:]]+/ ]]; then normalized+=' task5b_static_array_value ' fi normalized+="$line"$'\n' previous_continues=0 [[ "$line" != *'\' ]] || previous_continues=1 done <<<"$body" if [[ "$function_name" == fail ]]; then normalized="${normalized//\"\$*\"/task5b_static_message}" fi normalized="${normalized//\$\(\(/task5b_static_arithmetic_(}" printf -v "$destination_name" '%s' "$normalized" } task5b_gate0_fuse_body_is_closed() { local body="${1-}" exact_allowlist (( $# == 1 )) || return 1 [[ -n "$body" ]] || return 1 [[ "$body" != *'trap - DEBUG'* && "$body" != *'set +T'* && "$body" != *'shopt -u extdebug'* ]] || return 1 exact_allowlist='_k3slr_command | assert_eq | fail | task5b_gate0_active_matrix | task5b_gate0_assert_rejected | task5b_gate0_assert_route | task5b_gate0_fake_command | task5b_gate0_reset_route_record | task5b_gate0_route_equals | exit | local | shift | return | printf | read | :)' [[ "$body" == *"$exact_allowlist"* ]] || return 1 [[ "$body" != *'task5b_gate0_dynamic_external_helper'* ]] || return 1 [[ "$body" != *'*=*)'* ]] || return 1 } task5b_gate0_assignment_parser_body_is_closed() { local body="${1-}" (( $# == 1 )) || return 1 [[ -n "$body" ]] || return 1 if /usr/bin/grep -En \ '^[[:space:]]*(trap([[:space:]]|$)|set[[:space:]]+\+T([[:space:]]|$)|shopt[[:space:]].*extdebug|/|"/|command([[:space:]]|$)|builtin[[:space:]]+command([[:space:]]|$)|exec([[:space:]]|$)|eval([[:space:]]|$)|source([[:space:]]|$)|\.[[:space:]])' \ <<<"$body" >/dev/null; then return 1 fi } task5b_gate0_active_graph_is_closed() { local function_name body='' normalized_body='' for function_name in "${TASK5B_GATE0_ACTIVE_GRAPH_FUNCTIONS[@]}"; do body="$(declare -f "$function_name")" || return 1 if [[ "$function_name" == task5b_gate0_real_exec_fuse ]]; then task5b_gate0_fuse_body_is_closed "$body" || { printf 'Task 5B Gate 0 static fuse scan rejected: %s\n' \ "$function_name" >&2 return 1 } continue fi if [[ "$function_name" == task5b_gate0_assignment_only_status ]]; then task5b_gate0_assignment_parser_body_is_closed "$body" || { printf 'Task 5B Gate 0 assignment parser scan rejected: %s\n' \ "$function_name" >&2 return 1 } continue fi task5b_gate0_normalize_static_body_data_lines "$body" normalized_body \ "$function_name" || return 1 task5b_gate0_static_assignment_prefixes_are_closed "$body" || { printf 'Task 5B Gate 0 assignment-prefix scan rejected: %s\n' \ "$function_name" >&2 return 1 } task5b_gate0_static_execution_positions_are_closed "$normalized_body" || { printf 'Task 5B Gate 0 active graph scan rejected: %s\n' \ "$function_name" >&2 return 1 } done } task5b_gate0_active_graph_hash() { local destination_name="${1-}" function_name body='' graph_record='' (( $# == 1 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 for function_name in "${TASK5B_GATE0_ACTIVE_GRAPH_FUNCTIONS[@]}"; do body="$(declare -f "$function_name")" || return 1 graph_record+="${function_name}"$'\n'"${body}"$'\n' done body="$(printf '%s' "$graph_record" | /usr/bin/sha256sum)" || return 1 [[ "$body" =~ ^([0-9a-f]{64})[[:space:]]+-$ ]] || return 1 printf -v "$destination_name" '%s' "${BASH_REMATCH[1]}" } task5b_gate0_static_adversarial_contract() { local body='' body="$(declare -f task5b_gate0_static_safe_sample)" || return 1 assert_succeeds task5b_gate0_static_body_is_closed "$body" for adversary in task5b_gate0_adversary_direct task5b_gate0_adversary_indented \ task5b_gate0_adversary_control_word task5b_gate0_adversary_array \ task5b_gate0_adversary_variable task5b_gate0_adversary_shell_c \ task5b_gate0_adversary_trap_disable \ task5b_gate0_adversary_assignment_bare \ task5b_gate0_adversary_assignment_simple \ task5b_gate0_adversary_assignment_quoted \ task5b_gate0_adversary_assignment_indexed; do body="$(declare -f "$adversary")" || return 1 assert_fails task5b_gate0_static_body_is_closed "$body" done body="$(declare -f task5b_gate0_adversary_helper_root)"$'\n'\ "$(declare -f task5b_gate0_adversary_helper_leaf)" || return 1 assert_fails task5b_gate0_static_body_is_closed "$body" } task5b_gate0_full_graph_static_adversarial_contract() ( local scan_rc=0 task5b_gate0_assert_rejected() { TASK5B_ASSIGNMENT_PREFIX=1 trap - DEBUG /usr/bin/printf 'TASK5B_FULL_GRAPH_SCAN_CANARY' } task5b_gate0_active_graph_is_closed 2>/dev/null || scan_rc=$? assert_eq 1 "$scan_rc" \ 'actual full-graph scanner rejects a mutated allowlisted helper' ) task5b_gate0_dynamic_external_helper() { /usr/bin/printf 'TASK5B_REAL_EXEC_CANARY' } task5b_gate0_dynamic_fuse_contract() { local probe_rc=0 probe_output='' probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG /usr/bin/true )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks an unlisted absolute executable' assert_eq '' "$probe_output" 'unlisted absolute executable produces no output' probe_rc=0 probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG task5b_gate0_dynamic_external_helper )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks helper-mediated external execution' assert_eq '' "$probe_output" 'helper-mediated external canary never executes' probe_rc=0 probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 task5b_gate0_assert_rejected() { trap - DEBUG /usr/bin/printf 'TASK5B_TRAP_DISABLE_CANARY' } shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG task5b_gate0_assert_rejected )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks trap removal in an allowlisted helper' assert_eq '' "$probe_output" 'trap-removal helper canary never executes' probe_rc=0 probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG TASK5B_ASSIGNMENT_PREFIX= trap - DEBUG /usr/bin/printf 'TASK5B_BARE_ASSIGNMENT_CANARY' )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks a bare assignment prefix' assert_eq '' "$probe_output" 'bare assignment prefix leaves trap and canary closed' probe_rc=0 probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG TASK5B_ASSIGNMENT_PREFIX=1 /usr/bin/printf 'TASK5B_SIMPLE_ASSIGNMENT_CANARY' )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks a simple assignment prefix' assert_eq '' "$probe_output" 'simple assignment prefix external effect stays zero' probe_rc=0 probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG TASK5B_ASSIGNMENT_PREFIX="a b" trap - DEBUG /usr/bin/printf 'TASK5B_QUOTED_ASSIGNMENT_CANARY' )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks a quoted assignment prefix' assert_eq '' "$probe_output" 'quoted assignment prefix leaves trap and canary closed' probe_rc=0 probe_output="$( TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_FUSE_PROBE_SILENT=1 shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG TASK5B_ASSIGNMENT_PREFIX[0]=1 trap - DEBUG /usr/bin/printf 'TASK5B_INDEXED_ASSIGNMENT_CANARY' )" || probe_rc=$? assert_eq 96 "$probe_rc" 'generic fuse blocks an indexed assignment prefix' assert_eq '' "$probe_output" 'indexed assignment prefix leaves trap and canary closed' } task5b_gate0_reset_route_record() { TASK5B_GATE0_MODEL_DISPATCH_COUNT=0 TASK5B_GATE0_MODEL_EFFECT_COUNT=0 TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_LAST_ROUTE='' TASK5B_GATE0_LAST_OUTPUT='' : >"$TASK5B_GATE0_STDOUT_PATH" : >"$TASK5B_GATE0_STDERR_PATH" } task5b_gate0_assert_rejected() { local description="$1" rejected_rc=0 shift task5b_gate0_reset_route_record _k3slr_command "$@" >"$TASK5B_GATE0_STDOUT_PATH" \ 2>"$TASK5B_GATE0_STDERR_PATH" || rejected_rc=$? assert_eq 97 "$rejected_rc" "$description returns exact rc 97" assert_eq 0 "$TASK5B_GATE0_MODEL_DISPATCH_COUNT" "$description has model dispatch 0" assert_eq 0 "$TASK5B_GATE0_MODEL_EFFECT_COUNT" "$description has model effect 0" assert_eq 0 "$TASK5B_GATE0_ACTUAL_EXEC_COUNT" "$description has actual exec 0" [[ ! -s "$TASK5B_GATE0_STDOUT_PATH" && ! -s "$TASK5B_GATE0_STDERR_PATH" ]] || fail "$description emitted stdout or stderr" } task5b_gate0_assert_route() { local route_id="$1" route_rc=0 last_index swap local -a exact_arguments=() near_arguments=() shift exact_arguments=("$@") task5b_gate0_reset_route_record _k3slr_command "${exact_arguments[@]}" >"$TASK5B_GATE0_STDOUT_PATH" \ 2>"$TASK5B_GATE0_STDERR_PATH" || route_rc=$? assert_eq 0 "$route_rc" "$route_id accepts its exact argv" assert_eq 1 "$TASK5B_GATE0_MODEL_DISPATCH_COUNT" "$route_id dispatches exactly once" assert_eq 1 "$TASK5B_GATE0_MODEL_EFFECT_COUNT" "$route_id records exactly one model effect" assert_eq 0 "$TASK5B_GATE0_ACTUAL_EXEC_COUNT" "$route_id has actual exec 0" assert_eq "$route_id" "$TASK5B_GATE0_LAST_ROUTE" "$route_id records its route" assert_eq "modeled:${route_id}" "$TASK5B_GATE0_LAST_OUTPUT" "$route_id records output" [[ ! -s "$TASK5B_GATE0_STDOUT_PATH" && ! -s "$TASK5B_GATE0_STDERR_PATH" ]] || fail "$route_id exact route emitted stdout or stderr" near_arguments=("${exact_arguments[@]}" task5b-extra) task5b_gate0_assert_rejected "$route_id extra-argument near-match" \ "${near_arguments[@]}" last_index=$(( ${#exact_arguments[@]} - 1 )) near_arguments=("${exact_arguments[@]:0:last_index}") task5b_gate0_assert_rejected "$route_id missing-argument near-match" \ "${near_arguments[@]}" near_arguments=("${exact_arguments[@]}") near_arguments[last_index]=task5b-wrong-value task5b_gate0_assert_rejected "$route_id wrong-value near-match" \ "${near_arguments[@]}" if (( ${#exact_arguments[@]} >= 3 )); then near_arguments=("${exact_arguments[@]}") swap="${near_arguments[last_index - 1]}" near_arguments[last_index - 1]="${near_arguments[last_index]}" near_arguments[last_index]="$swap" task5b_gate0_assert_rejected "$route_id wrong-order near-match" \ "${near_arguments[@]}" near_arguments=("${exact_arguments[@]:0:last_index - 1}") near_arguments+=("${exact_arguments[last_index - 1]} ${exact_arguments[last_index]}") task5b_gate0_assert_rejected "$route_id wrong-grouping near-match" \ "${near_arguments[@]}" fi } task5b_gate0_static_fake_scan() { local fake_body route_body catchall_body fake_hash='' route_hash='' catchall_hash='' local active_graph_hash='' fake_body="$(declare -f task5b_gate0_fake_command)" || return 1 route_body="$(declare -f task5b_gate0_route_equals)" || return 1 catchall_body="$(declare -f _k3slr_command)" || return 1 task5b_gate0_static_body_is_closed "$fake_body" || return 1 task5b_gate0_static_body_is_closed "$catchall_body" || return 1 task5b_gate0_definition_hash fake_hash task5b_gate0_fake_command || return 1 task5b_gate0_definition_hash route_hash task5b_gate0_route_equals || return 1 task5b_gate0_definition_hash catchall_hash _k3slr_command || return 1 task5b_gate0_active_graph_is_closed || return 1 task5b_gate0_full_graph_static_adversarial_contract || return 1 task5b_gate0_active_graph_hash active_graph_hash || return 1 [[ "$fake_hash" == ef2cfc5af6fff12713b20f8734fd28114312d695a7df7d478f9bddd155398d09 ]] || return 1 [[ "$route_hash" == 53f6a8cdb6726626a1db2136d7c38e4f2c52c4eaaeb3671506fb504475586a19 ]] || return 1 [[ "$catchall_hash" == 76448cb3a82bf60c7904b7d6217762b2769c65069a5e54f086b960d5a63ca3ce ]] || return 1 [[ "$active_graph_hash" == 5b3cbe49958bfabe8556d2ec60cdfaf2fdcf586e41953e2681b1720399332293 ]] || return 1 [[ "$fake_body" == *'return 0'* && "$fake_body" == *'return 97'* ]] || return 1 [[ "$fake_body" != *'task5b_gate0_real_exec_fuse'* ]] || return 1 task5b_gate0_static_adversarial_contract } task5b_gate0_contract() ( local gate0_stdout="${fixture_root}/task5b-gate0.stdout" local gate0_stderr="${fixture_root}/task5b-gate0.stderr" local matrix_rc=0 # Foreground source-loaded fixture: production code is loaded, then only its # lowest command seam is replaced. No child/background fixture is started. # shellcheck source=/dev/null source "$LIBRARY_PATH" _k3slr_command() { task5b_gate0_fake_command "$@"; } exec 9>&2 TASK5B_GATE0_STDOUT_PATH="$gate0_stdout" TASK5B_GATE0_STDERR_PATH="$gate0_stderr" TASK5B_GATE0_MODEL_DISPATCH_COUNT=0 TASK5B_GATE0_MODEL_EFFECT_COUNT=0 TASK5B_GATE0_ACTUAL_EXEC_COUNT=0 TASK5B_GATE0_LAST_ROUTE='' TASK5B_GATE0_LAST_OUTPUT='' : >"$TASK5B_GATE0_STDOUT_PATH" : >"$TASK5B_GATE0_STDERR_PATH" task5b_gate0_active_matrix() { task5b_gate0_assert_route sudo-validate /usr/bin/sudo --validate task5b_gate0_assert_route sudo-cached-validate \ /usr/bin/sudo --non-interactive --validate task5b_gate0_assert_route apt-update \ /usr/bin/sudo --non-interactive -- /usr/bin/apt-get update task5b_gate0_assert_route apt-install \ /usr/bin/sudo --non-interactive -- /usr/bin/apt-get install --yes \ keepassxc=2.7.6+dfsg.1-1build3 cryptsetup-bin=2:2.7.0-1ubuntu4.2 task5b_gate0_assert_route feasibility-execute /usr/bin/bash \ /home/donghyeon/workspace/platform/scripts/validate/k3s-local-recovery-feasibility.sh \ --execute task5b_gate0_assert_route outer-scaffold-mkdir \ /usr/bin/sudo --non-interactive -- /usr/bin/mkdir --mode=0700 -- \ /mnt/k3s-recovery-ssd task5b_gate0_assert_route outer-mount \ /usr/bin/sudo --non-interactive -- /usr/bin/mount -t ntfs3 -o \ rw,uid=1000,gid=1000,dmask=0077,fmask=0177,nodev,nosuid,noexec \ /dev/disk/by-id/wwn-0x500a0751e6aa6254-part3 /mnt/k3s-recovery-ssd task5b_gate0_assert_route layout-root-mkdir /usr/bin/mkdir --mode=0700 -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery task5b_gate0_assert_route layout-vault-mkdir /usr/bin/mkdir --mode=0700 -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault task5b_gate0_assert_route layout-backups-mkdir /usr/bin/mkdir --mode=0700 -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault/backups task5b_gate0_assert_route layout-containers-mkdir /usr/bin/mkdir --mode=0700 -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers task5b_gate0_assert_route probe-mktemp /usr/bin/mktemp \ --tmpdir=/mnt/k3s-recovery-ssd/HyeonworksRecovery \ .k3slr-hardlink-probe.XXXXXXXX task5b_gate0_assert_route probe-link /usr/bin/ln -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1 \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1.link task5b_gate0_assert_route probe-unlink-link /usr/bin/unlink -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1.link task5b_gate0_assert_route probe-unlink-source /usr/bin/unlink -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-hardlink-probe.FIXTURE1 task5b_gate0_assert_route probe-sync /usr/bin/sync --file-system \ /mnt/k3s-recovery-ssd/HyeonworksRecovery task5b_gate0_assert_route keepass-db-create /usr/bin/keepassxc-cli db-create \ --quiet --set-password \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault/hyeonworks-recovery.kdbx task5b_gate0_assert_route keepass-add /usr/bin/keepassxc-cli add --quiet \ --generate --length 40 --lower --upper --numeric --every-group \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/vault/hyeonworks-recovery.kdbx \ 'K3s Recovery LUKS' task5b_gate0_assert_route container-dd /usr/bin/dd if=/dev/zero \ of=/mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks \ bs=16M count=2048 conv=excl,fsync status=progress task5b_gate0_assert_route outer-umount /usr/bin/sudo --non-interactive -- \ /usr/bin/umount -- /mnt/k3s-recovery-ssd task5b_gate0_assert_route loop-create /usr/bin/sudo --non-interactive -- \ /usr/sbin/losetup --find --show --nooverlap -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks task5b_gate0_assert_route luks-format /usr/bin/sudo --non-interactive -- \ /usr/sbin/cryptsetup luksFormat --batch-mode --type luks2 --key-file=- \ /dev/loop37 task5b_gate0_assert_route luks-open /usr/bin/sudo --non-interactive -- \ /usr/sbin/cryptsetup open --type luks2 --key-file=- /dev/loop37 k3s-recovery task5b_gate0_assert_route mkfs-ext4 /usr/bin/sudo --non-interactive -- \ /usr/sbin/mkfs.ext4 -F -L K3S_RECOVERY /dev/mapper/k3s-recovery task5b_gate0_assert_route inner-parent-mkdir /usr/bin/sudo --non-interactive -- \ /usr/bin/mkdir --mode=0700 -- /srv/recovery task5b_gate0_assert_route inner-mountpoint-mkdir \ /usr/bin/sudo --non-interactive -- /usr/bin/mkdir --mode=0700 -- \ /srv/recovery/k3s task5b_gate0_assert_route inner-mount /usr/bin/sudo --non-interactive -- \ /usr/bin/mount -t ext4 -o rw,nodev,nosuid,noexec \ /dev/mapper/k3s-recovery /srv/recovery/k3s task5b_gate0_assert_route inner-chown /usr/bin/sudo --non-interactive -- \ /usr/bin/chown root:root /srv/recovery/k3s task5b_gate0_assert_route inner-chmod /usr/bin/sudo --non-interactive -- \ /usr/bin/chmod 0700 /srv/recovery/k3s task5b_gate0_assert_route marker-mktemp /usr/bin/sudo --non-interactive -- \ /usr/bin/mktemp --tmpdir=/srv/recovery/k3s .k3slr-marker.tmp.XXXXXXXX task5b_gate0_assert_route marker-dd /usr/bin/sudo --non-interactive -- \ /usr/bin/dd of=/srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1 bs=37 count=1 \ iflag=fullblock conv=notrunc,fsync status=none task5b_gate0_assert_route outer-metadata-mktemp /usr/bin/mktemp \ --tmpdir=/mnt/k3s-recovery-ssd/HyeonworksRecovery \ .k3slr-volume.tmp.XXXXXXXX task5b_gate0_assert_route inner-metadata-mktemp \ /usr/bin/sudo --non-interactive -- /usr/bin/mktemp \ --tmpdir=/srv/recovery/k3s .k3slr-volume.tmp.XXXXXXXX task5b_gate0_assert_route outer-metadata-dd /usr/bin/dd \ of=/mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 \ bs=4096 count=1 iflag=fullblock conv=notrunc,fsync status=none task5b_gate0_assert_route inner-metadata-dd \ /usr/bin/sudo --non-interactive -- /usr/bin/dd \ if=/mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 \ of=/srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1 bs=4096 count=1 \ iflag=fullblock conv=notrunc,fsync status=none task5b_gate0_assert_route marker-stage-sync /usr/bin/sudo --non-interactive -- \ /usr/bin/sync --file-system /srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1 task5b_gate0_assert_route marker-publish /usr/bin/sudo --non-interactive -- \ /usr/bin/ln -- /srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1 \ /srv/recovery/k3s/.recovery-marker task5b_gate0_assert_route outer-stage-sync /usr/bin/sync --file-system \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 task5b_gate0_assert_route inner-stage-sync /usr/bin/sudo --non-interactive -- \ /usr/bin/sync --file-system /srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1 task5b_gate0_assert_route outer-metadata-publish /usr/bin/ln -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/volume.env task5b_gate0_assert_route inner-metadata-publish \ /usr/bin/sudo --non-interactive -- /usr/bin/ln -- \ /srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1 \ /srv/recovery/k3s/.recovery-volume.env task5b_gate0_assert_route marker-stage-unlink \ /usr/bin/sudo --non-interactive -- /usr/bin/unlink -- \ /srv/recovery/k3s/.k3slr-marker.tmp.FIXTURE1 task5b_gate0_assert_route outer-stage-unlink /usr/bin/unlink -- \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/.k3slr-volume.tmp.FIXTURE1 task5b_gate0_assert_route inner-stage-unlink \ /usr/bin/sudo --non-interactive -- /usr/bin/unlink -- \ /srv/recovery/k3s/.k3slr-volume.tmp.FIXTURE1 task5b_gate0_assert_route inner-parent-sync /usr/bin/sudo --non-interactive -- \ /usr/bin/sync --file-system /srv/recovery/k3s task5b_gate0_assert_route inner-umount /usr/bin/sudo --non-interactive -- \ /usr/bin/umount -- /srv/recovery/k3s task5b_gate0_assert_route mapping-close /usr/bin/sudo --non-interactive -- \ /usr/sbin/cryptsetup close k3s-recovery task5b_gate0_assert_route loop-detach /usr/bin/sudo --non-interactive -- \ /usr/sbin/losetup --detach /dev/loop37 task5b_gate0_assert_rejected 'unknown executable' \ /fixture/task5b-unknown --execute } # This scan must pass before the first exact hazardous argv is presented to # the fake. It is independent of every dynamic route assertion below. task5b_gate0_static_fake_scan || return 1 task5b_gate0_dynamic_fuse_contract || return 1 # The active DEBUG trap exists only inside this foreground nested subshell. # Returning from the subshell removes it without an allowlisted teardown word. ( shopt -s extdebug set -T trap 'task5b_gate0_real_exec_fuse' DEBUG task5b_gate0_active_matrix ) || matrix_rc=$? (( matrix_rc == 0 )) || return "$matrix_rc" exec 9>&- /usr/bin/rm -- "$TASK5B_GATE0_STDOUT_PATH" "$TASK5B_GATE0_STDERR_PATH" ) # Production lifecycle probes need a second boundary beyond their command # seam doubles: if production regresses to a direct executable, the DEBUG trap # must reject that simple command before Bash executes it. Runtime membership # and the pre-trap static scan share this exact, profile-scoped function set. readonly -A TASK5B_PRODUCTION_PROBE_ALLOWED_FUNCTIONS=( [unavailable/_k3slr_lifecycle_action]=1 [unavailable/_k3slr_command]=1 [unavailable/task5b_unavailable_probe_payload]=1 [unavailable/task5b_production_probe_emit_ok]=1 [legacy_binding/_k3slr_lifecycle_action]=1 [legacy_binding/_k3slr_command]=1 [legacy_binding/task5b_legacy_binding_probe_payload]=1 [legacy_binding/task5b_production_probe_emit_ok]=1 [api_dispatch/_k3slr_lifecycle_dispatch]=1 [api_dispatch/_k3slr_lifecycle_step]=1 [api_dispatch/_k3slr_lifecycle_action]=1 [api_dispatch/_k3slr_command]=1 [api_dispatch/task5b_api_dispatch_probe_payload]=1 [api_dispatch/task5b_production_probe_emit_ok]=1 [api_direct/_k3slr_lifecycle_action]=1 [api_direct/_k3slr_command]=1 [api_direct/task5b_api_direct_probe_payload]=1 [api_direct/task5b_production_probe_emit_ok]=1 [api_main_arity/_k3slr_lifecycle_main]=1 [api_main_arity/task5b_api_main_arity_probe_payload]=1 [api_main_arity/task5b_production_probe_emit_ok]=1 [action1_main/_k3slr_lifecycle_main]=1 [action1_main/_k3slr_load_contract]=1 [action1_main/_k3slr_parse_contract]=1 [action1_main/_k3slr_contract_value_is_valid]=1 [action1_main/_k3slr_uint]=1 [action1_main/_k3slr_require_execute_tty]=1 [action1_main/_k3slr_tty_capability]=1 [action1_main/_k3slr_lifecycle_dispatch]=1 [action1_main/_k3slr_lifecycle_step]=1 [action1_main/_k3slr_lifecycle_action]=1 [action1_main/_k3slr_command]=1 [action1_main/task5b_gate0_route_equals]=1 [action1_main/task5b_action1_main_probe_payload]=1 [action1_main/task5b_production_probe_emit_ok]=1 [action1_alternate/_k3slr_lifecycle_action]=1 [action1_alternate/_k3slr_command]=1 [action1_alternate/task5b_gate0_route_equals]=1 [action1_alternate/task5b_action1_alternate_probe_payload]=1 [action1_alternate/task5b_production_probe_emit_ok]=1 [canary/task5b_production_probe_canary_helper_root]=1 [canary/task5b_production_probe_canary_helper_leaf]=1 ) readonly -A TASK5B_PRODUCTION_PROBE_EXPECTED_HASHES=( [core/task5b_production_probe_profile_is_known]=ed04cce41523a02acbebea479607e696dc5ab7ec1486a4914230b74572cd85af [core/task5b_production_probe_function_is_allowed]=559b5481974916559ae305975aaab82f88a542dc53547dee4575d599d62903ea [core/task5b_production_probe_builtin_is_allowed]=58cb03fd47554c9cf9ef4b52372e7125b86c86cd88b23364ff31c41e41e1c84b [core/task5b_production_probe_debug]=662c89fd5213726938630c16b475d894f5a19ec922a8c09dc7563312c55e38a7 [core/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [core/task5b_production_probe_canary_graph_is_pinned]=c3c0724de2a4d7da8d20739d5f66d46dc54bde69c2e791d3171d2e4c72392ac5 [core/task5b_production_probe_body_is_closed]=bc1d27a01debe57be58252f402ddd958fbd9c4fe73da5a4a2c7cfef7851e067d [core/task5b_production_probe_arm]=e391120fe268bbfb2b67c8d6cb23591fa9f5635dc1549a9f066c4aba711d5c34 [core/task5b_gate0_assignment_only_status]=075118e7edcd50ef5722f01874d2b138b4ab6cc483a6c17be517bc9a45df7af8 [unavailable/_k3slr_command]=8634e9ebe9c34a48eff4381729ce6883b95a69a69c6c7218498b6db8a7806248 [unavailable/task5b_unavailable_probe_payload]=e83567872c19a73a08baa434eb80e449f493505d6945c4d4f4cae2ffedaa2435 [unavailable/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [unavailable/_k3slr_lifecycle_action]=a499b7694c449633f3ce538e8296b1d543d04565c046508587d18b677aa12384 [legacy_binding/task5b_legacy_binding_probe_payload]=46d73155080f35fdd4c9ec1d531ed50f6ac77447b6389549d934f862e4430316 [legacy_binding/_k3slr_lifecycle_action]=a499b7694c449633f3ce538e8296b1d543d04565c046508587d18b677aa12384 [legacy_binding/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [legacy_binding/_k3slr_command]=8634e9ebe9c34a48eff4381729ce6883b95a69a69c6c7218498b6db8a7806248 [api_dispatch/_k3slr_command]=8634e9ebe9c34a48eff4381729ce6883b95a69a69c6c7218498b6db8a7806248 [api_dispatch/_k3slr_lifecycle_step]=1cf91eba1179fbb35f58ca20dbf0e62f2ff3b754cc64f6e4c6547c1a1193b212 [api_dispatch/task5b_api_dispatch_probe_payload]=341b9a6990134bc9f95fb881e4596a4db94fbfbc4303e8afe61720f1684958e4 [api_dispatch/_k3slr_lifecycle_action]=a499b7694c449633f3ce538e8296b1d543d04565c046508587d18b677aa12384 [api_dispatch/_k3slr_lifecycle_dispatch]=e407e698b9a69c47c9969db009211e47614c9753f701fa99f82a6d9e84c82477 [api_dispatch/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [api_dispatch/task5b_api_state_debug_observer]=c9bec0cfb5204ee77ac32fab9eb31b00cf033d6fd3ac897a98878991ca13897f [api_direct/_k3slr_lifecycle_action]=a499b7694c449633f3ce538e8296b1d543d04565c046508587d18b677aa12384 [api_direct/_k3slr_command]=8634e9ebe9c34a48eff4381729ce6883b95a69a69c6c7218498b6db8a7806248 [api_direct/task5b_api_direct_probe_payload]=c4723fd1f9a04b2095a1935c21c5cebca01f2e13c75f9ceda59d58ad1a909332 [api_direct/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [api_main_arity/task5b_api_main_arity_probe_payload]=5c23d4c2810c534318d210de7c35a6ac7f0fbf52ab88edbd421c30d2c534ff8a [api_main_arity/_k3slr_lifecycle_main]=677f7f482840af5c5a7dc37c4f7abe6a470a24ed108030cb14fe1f3b1c8ae53f [api_main_arity/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [action1_main/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [action1_main/_k3slr_contract_value_is_valid]=b8eab6a6688c3aac2456bf9a4987eeeefec673a1dccf5e80e785cc9dcbddc7dd [action1_main/_k3slr_lifecycle_action]=a499b7694c449633f3ce538e8296b1d543d04565c046508587d18b677aa12384 [action1_main/task5b_action1_main_probe_payload]=ac0431e91d90989ae9eb49dd7ebcecbc12a87f99f50771db0fce62ce9960aa7c [action1_main/_k3slr_parse_contract]=a27e7c60983ecb4792349c69bc6020ee51dfdd6f0adefbf4359b54b20cf64b0d [action1_main/_k3slr_uint]=e60445f9cc78399afebda8d2010fbd64ceae23da4cfa15373c60122127df6945 [action1_main/task5b_gate0_route_equals]=53f6a8cdb6726626a1db2136d7c38e4f2c52c4eaaeb3671506fb504475586a19 [action1_main/_k3slr_tty_capability]=c59e220dbcc6f3d9af8707cc365c456ee404d20deed8150171a8fb37d87a7497 [action1_main/_k3slr_load_contract]=71855162c947b01b145e79aa4e30a448c21690efa183c56ccafe97b776912cce [action1_main/_k3slr_require_execute_tty]=9d194fe3ba36a7bd25aaa5b53eefeafa4d23fe8f307995be7a9039472b42a838 [action1_main/_k3slr_command]=6e84784f49cc5e8072cdd4c8c0cccbd20ef15e5886228946d1c7c670967a3ec2 [action1_main/_k3slr_lifecycle_main]=677f7f482840af5c5a7dc37c4f7abe6a470a24ed108030cb14fe1f3b1c8ae53f [action1_main/_k3slr_lifecycle_dispatch]=e407e698b9a69c47c9969db009211e47614c9753f701fa99f82a6d9e84c82477 [action1_main/_k3slr_lifecycle_step]=1cf91eba1179fbb35f58ca20dbf0e62f2ff3b754cc64f6e4c6547c1a1193b212 [action1_main/task5b_action1_debug_observer]=ecaf3a7e1ee93fd5f330301b8fc21c0300834bd5ec376c766af704de83271cee [action1_alternate/task5b_gate0_route_equals]=53f6a8cdb6726626a1db2136d7c38e4f2c52c4eaaeb3671506fb504475586a19 [action1_alternate/_k3slr_lifecycle_action]=a499b7694c449633f3ce538e8296b1d543d04565c046508587d18b677aa12384 [action1_alternate/_k3slr_command]=6e84784f49cc5e8072cdd4c8c0cccbd20ef15e5886228946d1c7c670967a3ec2 [action1_alternate/task5b_action1_alternate_probe_payload]=aa5693a60539d54eda18fdefef47c7ad20891d94d0478df41a0afa46860565cc [action1_alternate/task5b_production_probe_emit_ok]=278abe2c9fb6b744caa3a5517ebc8b504bee1409df4819e6fb9be1129b22fbd0 [canary/task5b_production_probe_canary_helper_root]=d14b146f01b0a6ef5a36be1d613994901683a503661348195b5c2eeca7b9aa9b [canary/task5b_production_probe_canary_helper_leaf]=a94ebf016f987960b669e5a7c9b776e95b7d305675dc74868b46bc25460810a9 ) task5b_production_probe_profile_is_known() { (( $# == 1 )) || return 1 case "$1" in unavailable|legacy_binding|api_dispatch|api_direct|api_main_arity|\ action1_main|action1_alternate|canary) return 0 ;; *) return 1 ;; esac } task5b_production_probe_function_is_allowed() { local profile="${1-}" function_name="${2-}" (( $# == 2 )) || return 1 task5b_production_probe_profile_is_known "$profile" || return 1 [[ "${TASK5B_PRODUCTION_PROBE_ALLOWED_FUNCTIONS["$profile/$function_name"]+present}" == present ]] } task5b_production_probe_builtin_is_allowed() { local profile="${1-}" builtin_name="${2-}" (( $# == 2 )) || return 1 task5b_production_probe_profile_is_known "$profile" || return 1 case "$profile" in canary) [[ "$builtin_name" == printf ]] ;; action1_main) case "$builtin_name" in declare|local|printf|return|shift) return 0 ;; *) return 1 ;; esac ;; *) case "$builtin_name" in local|printf|return|shift) return 0 ;; *) return 1 ;; esac ;; esac } task5b_production_probe_debug() { local -a saved_bash_rematch=("${BASH_REMATCH[@]}") local candidate="${1-}" observed_step="${2-}" execution_word='' local assignment_rc=0 (( $# == 2 )) || exit 96 task5b_production_probe_profile_is_known "$task5b_probe_profile" || exit 96 case "$task5b_probe_profile" in api_dispatch) task5b_api_state_debug_observer "$candidate" || { printf 'TASK5B_PROBE_BLOCK|%s|1\n' "$task5b_probe_profile" >&9 exit 96 } ;; action1_main) task5b_action1_debug_observer "$candidate" "$observed_step" || { printf 'TASK5B_PROBE_BLOCK|%s|1\n' "$task5b_probe_profile" >&9 exit 96 } ;; esac candidate="${candidate#"${candidate%%[![:space:]]*}"}" while [[ "$candidate" =~ ^(if|elif|while|until|then|\!|time)[[:space:]]+(.+)$ ]]; do candidate="${BASH_REMATCH[2]}" done case "$candidate" in ''|'{'|'}'|'[['*|'(('*|for\ *|case\ *|do|done|esac) BASH_REMATCH=("${saved_bash_rematch[@]}") return 0 ;; esac if [[ "$task5b_probe_profile" == action1_main ]]; then case "$candidate" in 'IFS= read -r line'|'seen["$key"]=1'|'result["$key"]="$value"') BASH_REMATCH=("${saved_bash_rematch[@]}") return 0 ;; esac fi if [[ "$candidate" =~ ^[a-zA-Z_][a-zA-Z0-9_]*(\[[^]]+\])?\+?= ]]; then task5b_gate0_assignment_only_status "$candidate" || assignment_rc=$? if (( assignment_rc == 0 )); then BASH_REMATCH=("${saved_bash_rematch[@]}") return 0 fi printf 'TASK5B_PROBE_BLOCK|%s|1\n' "$task5b_probe_profile" >&9 exit 96 fi read -r execution_word _ <<<"$candidate" execution_word="${execution_word%;}" case "$execution_word" in \$*|\"\$*|\'\$*|/*|\"/*|\'/*|sudo|command|builtin|exec|eval|source|.|\ trap|set|shopt|unset|enable|function) printf 'TASK5B_PROBE_BLOCK|%s|1\n' "$task5b_probe_profile" >&9 exit 96 ;; esac if task5b_production_probe_builtin_is_allowed "$task5b_probe_profile" \ "$execution_word" || task5b_production_probe_function_is_allowed "$task5b_probe_profile" \ "$execution_word"; then BASH_REMATCH=("${saved_bash_rematch[@]}") return 0 fi printf 'TASK5B_PROBE_BLOCK|%s|1\n' "$task5b_probe_profile" >&9 exit 96 } task5b_production_probe_emit_ok() { (( $# >= 1 )) || return 1 printf 'TASK5B_PROBE_OK|%s' "$1" >&9 shift printf '|%s' "$@" >&9 printf '\n' >&9 } task5b_production_probe_canary_graph_is_pinned() { local entry function_name actual_hash='' expected_hash='' function_count=0 (( $# == 0 )) || return 1 for entry in "${!TASK5B_PRODUCTION_PROBE_ALLOWED_FUNCTIONS[@]}"; do [[ "$entry" == canary/* ]] || continue function_name="${entry#*/}" (( function_count += 1 )) expected_hash="${TASK5B_PRODUCTION_PROBE_EXPECTED_HASHES[$entry]-}" [[ "$expected_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 task5b_gate0_definition_hash actual_hash "$function_name" || return 1 [[ "$actual_hash" == "$expected_hash" ]] || return 1 done (( function_count == 2 )) } task5b_production_probe_body_is_closed() { local profile="${1-}" function_name="${2-}" body="${3-}" local line='' scan_body='' character='' next_character='' quote='' local mode=normal escaped=0 index=0 length=0 line_number=0 local allowed_redirection_count=0 expected_redirection_count=0 (( $# == 3 )) || return 1 task5b_production_probe_profile_is_known "$profile" || return 1 [[ "$function_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ && -n "$body" ]] || return 1 case "$profile/$function_name" in */task5b_production_probe_emit_ok) expected_redirection_count=3 ;; action1_main/_k3slr_parse_contract|*/_k3slr_lifecycle_main) expected_redirection_count=1 ;; esac while IFS= read -r line; do if [[ "$function_name" == task5b_production_probe_emit_ok ]]; then case "$line" in " printf 'TASK5B_PROBE_OK|%s' \"\$1\" 1>&9;"|\ " printf '|%s' \"\$@\" 1>&9;"|" printf '\\n' 1>&9") (( allowed_redirection_count += 1 )) continue ;; esac elif [[ "$profile/$function_name" == action1_main/_k3slr_parse_contract && "$line" == " done < \"\$file\";" ]]; then (( allowed_redirection_count += 1 )) continue elif [[ "$function_name" == _k3slr_lifecycle_main && "$line" == ' declare -F _k3slr_prove_header_restore > /dev/null || return 1;' ]]; then (( allowed_redirection_count += 1 )) continue fi scan_body+="$line"$'\n' done <<<"$body" (( allowed_redirection_count == expected_redirection_count )) || return 1 [[ "$scan_body" != *'<('* && "$scan_body" != *'>('* && "$scan_body" != *'trap - DEBUG'* && "$scan_body" != *'set +T'* && "$scan_body" != *'shopt -u extdebug'* && "$scan_body" != *'unset '* && "$scan_body" != *'enable '* ]] || return 1 while IFS= read -r line; do (( line_number += 1 )) if (( line_number > 1 )) && [[ "$line" =~ ^[[:space:]]*(function[[:space:]]+|[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(\)) ]]; then return 1 fi quote='' mode=normal escaped=0 index=0 length="${#line}" while (( index < length )); do character="${line:index:1}" next_character='' (( index + 1 >= length )) || next_character="${line:index + 1:1}" if (( escaped == 1 )); then escaped=0 (( index += 1 )) continue fi case "$quote" in single) [[ "$character" != "'" ]] || quote='' (( index += 1 )) continue ;; double) case "$character" in '\\') escaped=1 ;; '"') quote='' ;; esac (( index += 1 )) continue ;; esac case "$mode" in conditional) if [[ "$character$next_character" == ']]' ]]; then mode=normal (( index += 2 )) else (( index += 1 )) fi continue ;; arithmetic) if [[ "$character$next_character" == '))' ]]; then mode=normal (( index += 2 )) else (( index += 1 )) fi continue ;; esac case "$character" in "'") quote=single ;; '"') quote=double ;; '\\') escaped=1 ;; '[') if [[ "$next_character" == '[' ]]; then mode=conditional (( index += 2 )) continue fi ;; '(') if [[ "$next_character" == '(' ]]; then mode=arithmetic (( index += 2 )) continue fi ;; '<'|'>') return 1 ;; esac (( index += 1 )) done done <<<"$scan_body" } task5b_production_probe_core_is_closed() { local function_name actual_hash='' expected_hash='' local -a core_functions=( task5b_production_probe_profile_is_known task5b_production_probe_function_is_allowed task5b_production_probe_builtin_is_allowed task5b_production_probe_debug task5b_production_probe_emit_ok task5b_production_probe_canary_graph_is_pinned task5b_production_probe_body_is_closed task5b_production_probe_arm task5b_gate0_assignment_only_status ) (( $# == 0 )) || return 1 for function_name in "${core_functions[@]}"; do expected_hash="${TASK5B_PRODUCTION_PROBE_EXPECTED_HASHES["core/$function_name"]-}" [[ "$expected_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 task5b_gate0_definition_hash actual_hash "$function_name" || return 1 [[ "$actual_hash" == "$expected_hash" ]] || { printf 'Task 5B production probe core hash mismatch: %s expected=%s actual=%s\n' \ "$function_name" "$expected_hash" "$actual_hash" >&2 return 1 } done } task5b_production_probe_static_graph_is_closed() { local profile="${1-}" entry function_name body='' normalized_body='' local actual_hash='' expected_hash='' function_count=0 line='' line_number=0 (( $# == 1 )) || return 1 task5b_production_probe_profile_is_known "$profile" || return 1 [[ "$profile" != canary ]] || return 1 task5b_production_probe_core_is_closed || return 1 if [[ "$profile" == api_dispatch ]]; then function_name=task5b_api_state_debug_observer entry="$profile/$function_name" expected_hash="${TASK5B_PRODUCTION_PROBE_EXPECTED_HASHES[$entry]-}" [[ "$expected_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 task5b_gate0_definition_hash actual_hash "$function_name" || return 1 [[ "$actual_hash" == "$expected_hash" ]] || { printf 'Task 5B production probe observer hash mismatch: %s expected=%s actual=%s\n' \ "$function_name" "$expected_hash" "$actual_hash" >&2 return 1 } elif [[ "$profile" == action1_main ]]; then function_name=task5b_action1_debug_observer entry="$profile/$function_name" expected_hash="${TASK5B_PRODUCTION_PROBE_EXPECTED_HASHES[$entry]-}" [[ "$expected_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 task5b_gate0_definition_hash actual_hash "$function_name" || return 1 [[ "$actual_hash" == "$expected_hash" ]] || { printf 'Task 5B production probe observer hash mismatch: %s expected=%s actual=%s\n' \ "$function_name" "$expected_hash" "$actual_hash" >&2 return 1 } fi for entry in "${!TASK5B_PRODUCTION_PROBE_ALLOWED_FUNCTIONS[@]}"; do [[ "$entry" == "$profile/"* ]] || continue function_name="${entry#*/}" (( function_count += 1 )) expected_hash="${TASK5B_PRODUCTION_PROBE_EXPECTED_HASHES[$entry]-}" [[ "$expected_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 task5b_gate0_definition_hash actual_hash "$function_name" || return 1 [[ "$actual_hash" == "$expected_hash" ]] || { printf 'Task 5B production probe profile hash mismatch: %s expected=%s actual=%s\n' \ "$entry" "$expected_hash" "$actual_hash" >&2 return 1 } body="$(declare -f "$function_name")" || return 1 task5b_production_probe_body_is_closed "$profile" "$function_name" \ "$body" || { printf 'Task 5B production probe body scan failed: %s\n' "$entry" >&2 return 1 } [[ "$body" != *'<('* && "$body" != *'>('* && "$body" != *'trap - DEBUG'* && "$body" != *'set +T'* && "$body" != *'shopt -u extdebug'* && "$body" != *'unset '* && "$body" != *'enable '* ]] || { printf 'Task 5B production probe forbidden token: %s\n' "$entry" >&2 return 1 } line_number=0 while IFS= read -r line; do (( line_number += 1 )) if (( line_number > 1 )) && [[ "$line" =~ ^[[:space:]]*(function[[:space:]]+|[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(\)) ]]; then printf 'Task 5B production probe nested definition: %s\n' "$entry" >&2 return 1 fi done <<<"$body" task5b_gate0_normalize_static_body_data_lines "$body" normalized_body \ "$function_name" || { printf 'Task 5B production probe normalization failed: %s\n' "$entry" >&2 return 1 } if [[ "$function_name" != _k3slr_parse_contract ]]; then task5b_gate0_static_assignment_prefixes_are_closed "$body" || { printf 'Task 5B production probe assignment scan failed: %s\n' "$entry" >&2 return 1 } fi task5b_gate0_static_execution_positions_are_closed "$normalized_body" || { printf 'Task 5B production probe execution scan failed: %s\n' "$entry" >&2 return 1 } done (( function_count > 1 )) || return 1 } task5b_production_probe_arm() { (( $# == 1 )) || return 1 task5b_production_probe_profile_is_known "$1" || return 1 task5b_probe_profile="$1" shopt -s extdebug set -T trap 'task5b_production_probe_debug "${BASH_COMMAND-}" "${step-}"' DEBUG } task5b_production_probe_canary_helper_root() { task5b_production_probe_canary_helper_leaf } task5b_production_probe_canary_helper_leaf() { /usr/bin/printf 'TASK5B_ACTION1_HELPER_EXEC_CANARY' } task5b_production_probe_canary_unexpected_helper() { /usr/bin/printf 'TASK5B_UNEXPECTED_HELPER_EXEC_CANARY' } task5b_production_probe_static_redirection_adversary() { printf 'TASK5B_REDIRECTION_CANARY' >"$fixture_root/task5b-redirection-canary" } task5b_production_probe_static_process_substitution_adversary() { : < <(/usr/bin/printf 'TASK5B_PROCESS_SUBSTITUTION_CANARY') } task5b_production_probe_static_redefinition_adversary() { task5b_nested_redefinition() { :; } } task5b_production_probe_adversary_matrix() ( local adversary='' record='' probe_rc=0 body='' graph_body='' local -a adversaries=( direct helper variable array command-wrapper builtin-wrapper exec-wrapper eval-wrapper source-wrapper unexpected-helper trap-disable functrace-disable extdebug-disable unset-wrapper enable-wrapper ) task5b_production_probe_core_is_closed || return 1 task5b_production_probe_canary_graph_is_pinned || return 1 graph_body="$(declare -f task5b_production_probe_static_graph_is_closed)" || return 1 [[ "$graph_body" == *'task5b_production_probe_body_is_closed "$profile" "$function_name" "$body"'* ]] || fail 'production graph closure uses the same body scanner as static adversaries' for adversary in "${adversaries[@]}"; do record='' probe_rc=0 record="$( local runner='' local -a runners=() exec 9>&1 exec 2>&1 task5b_production_probe_arm canary || return 1 case "$adversary" in direct) /usr/bin/printf 'TASK5B_DIRECT_EXEC_CANARY' ;; helper) task5b_production_probe_canary_helper_root ;; variable) runner=/usr/bin/printf "$runner" 'TASK5B_VARIABLE_EXEC_CANARY' ;; array) runners=(/usr/bin/printf TASK5B_ARRAY_EXEC_CANARY) "${runners[@]}" ;; command-wrapper) command /usr/bin/printf 'TASK5B_COMMAND_EXEC_CANARY' ;; builtin-wrapper) builtin command /usr/bin/printf 'TASK5B_BUILTIN_EXEC_CANARY' ;; exec-wrapper) exec /usr/bin/printf 'TASK5B_EXEC_CANARY' ;; eval-wrapper) eval '/usr/bin/printf TASK5B_EVAL_CANARY' ;; source-wrapper) source /fixture/task5b-source-canary ;; unexpected-helper) task5b_production_probe_canary_unexpected_helper ;; trap-disable) trap - DEBUG ;; functrace-disable) set +T ;; extdebug-disable) shopt -u extdebug ;; unset-wrapper) unset task5b_probe_profile ;; enable-wrapper) enable -n printf ;; *) return 1 ;; esac printf 'TASK5B_CANARY_ESCAPED' )" || probe_rc=$? assert_eq 96 "$probe_rc" "production probe fuse blocks adversary: $adversary" assert_eq 'TASK5B_PROBE_BLOCK|canary|1' "$record" \ "production probe adversary has one block record and no effect: $adversary" done body="$(declare -f task5b_production_probe_static_redirection_adversary)" || return 1 assert_fails task5b_production_probe_body_is_closed canary \ task5b_production_probe_static_redirection_adversary "$body" body="$(declare -f task5b_production_probe_static_process_substitution_adversary)" || return 1 assert_fails task5b_production_probe_body_is_closed canary \ task5b_production_probe_static_process_substitution_adversary "$body" body="$(declare -f task5b_production_probe_static_redefinition_adversary)" || return 1 assert_fails task5b_production_probe_body_is_closed canary \ task5b_production_probe_static_redefinition_adversary "$body" ) task5b_mountinfo_counts_record() { local record="${1-}" source_destination="${2-}" outer_destination="${3-}" local inner_destination="${4-}" name line target source field_index local source_count=0 outer_count=0 inner_count=0 local -a fields=() (( $# == 4 )) || return 1 for name in "$source_destination" "$outer_destination" "$inner_destination"; do [[ "$name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 done while IFS= read -r line; do [[ -n "$line" ]] || continue read -r -a fields <<<"$line" (( ${#fields[@]} >= 10 )) || return 1 target="${fields[4]}" source='' for (( field_index=0; field_index<${#fields[@]}; field_index++ )); do if [[ "${fields[field_index]}" == - ]]; then (( field_index + 2 < ${#fields[@]} )) || return 1 source="${fields[field_index + 2]}" break fi done [[ -n "$source" ]] || return 1 [[ "$source" != /dev/sda3 ]] || (( source_count += 1 )) [[ "$target" != /mnt/k3s-recovery-ssd ]] || (( outer_count += 1 )) [[ "$target" != /srv/recovery/k3s ]] || (( inner_count += 1 )) done <<<"$record" printf -v "$source_destination" '%s' "$source_count" printf -v "$outer_destination" '%s' "$outer_count" printf -v "$inner_destination" '%s' "$inner_count" } task5b_loop_backing_is_recovery() { (( $# == 1 )) || return 1 case "$1" in /fixture/recovery-partition|\ /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks) return 0 ;; *) return 1 ;; esac } task5b_read_exact_single_line() { local destination_name="${1-}" source_path="${2-}" local -a source_lines=() (( $# == 2 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 [[ -r "$source_path" ]] || return 1 mapfile -t source_lines <"$source_path" || return 1 (( ${#source_lines[@]} == 1 )) || return 1 [[ -n "${source_lines[0]}" ]] || return 1 printf -v "$destination_name" '%s' "${source_lines[0]}" } task5b_loop_identity_count_from_record() { local record="${1-}" recovery_major_minor="${2-}" destination_name="${3-}" local line loop_name loop_minor back_inode back_major_minor loop_major_minor local offset sizelimit diskseq_before cached_diskseq diskseq_after local encoded_filename extra identity_match_count=0 local -A seen_loop_names=() (( $# == 3 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 [[ "$recovery_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1 while IFS= read -r line; do [[ -n "$line" ]] || continue IFS='|' read -r loop_name back_inode back_major_minor loop_major_minor \ offset sizelimit diskseq_before cached_diskseq diskseq_after \ encoded_filename extra <<<"$line" [[ -z "${extra:-}" ]] || return 1 [[ "$loop_name" =~ ^/dev/loop([0-9]+)$ ]] || return 1 loop_minor="${BASH_REMATCH[1]}" [[ -z "${seen_loop_names[$loop_name]+present}" ]] || return 1 seen_loop_names["$loop_name"]=1 [[ "$back_inode" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$back_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1 [[ "$loop_major_minor" == "7:${loop_minor}" ]] || return 1 [[ "$offset" =~ ^[0-9]+$ && "$sizelimit" =~ ^[0-9]+$ ]] || return 1 [[ "$diskseq_before" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$cached_diskseq" == "$diskseq_before" ]] || return 1 [[ "$diskseq_after" == "$diskseq_before" ]] || return 1 [[ "$encoded_filename" =~ ^([a-zA-Z0-9._/:+-]|\\x[0-9a-fA-F]{2})+$ ]] || return 1 [[ "$back_major_minor" != "$recovery_major_minor" ]] || (( identity_match_count += 1 )) done <<<"$record" printf -v "$destination_name" '%s' "$identity_match_count" } task5b_loop_live_identity_line() { local loop_name="${1-}" recovery_major_minor="${2-}" destination_name="${3-}" local loop_minor local sysfs_root loop_major_minor_before='' loop_major_minor_after='' local diskseq_before='' diskseq_after='' backing_before='' backing_after='' local offset_before='' offset_after='' sizelimit_before='' sizelimit_after='' local udev_path line value back_major_minor='' back_inode='' local encoded_filename='' cached_diskseq='' field_name local back_major_minor_count=0 back_inode_count=0 encoded_filename_count=0 local cached_diskseq_count=0 (( $# == 3 )) || return 1 [[ "$loop_name" =~ ^loop([0-9]+)$ ]] || return 1 loop_minor="${BASH_REMATCH[1]}" [[ "$recovery_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 sysfs_root="/sys/class/block/${loop_name}" task5b_read_exact_single_line loop_major_minor_before "${sysfs_root}/dev" || return 1 task5b_read_exact_single_line diskseq_before "${sysfs_root}/diskseq" || return 1 task5b_read_exact_single_line backing_before \ "${sysfs_root}/loop/backing_file" || return 1 task5b_read_exact_single_line offset_before "${sysfs_root}/loop/offset" || return 1 task5b_read_exact_single_line sizelimit_before \ "${sysfs_root}/loop/sizelimit" || return 1 [[ "$loop_major_minor_before" == "7:${loop_minor}" ]] || return 1 [[ "$diskseq_before" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$offset_before" =~ ^[0-9]+$ && "$sizelimit_before" =~ ^[0-9]+$ ]] || return 1 udev_path="/run/udev/data/b${loop_major_minor_before}" [[ -r "$udev_path" ]] || return 1 while IFS= read -r line; do case "$line" in E:ID_LOOP_BACKING_DEVICE=*) value="${line#E:ID_LOOP_BACKING_DEVICE=}" (( back_major_minor_count += 1 )) back_major_minor="$value" ;; E:ID_LOOP_BACKING_INODE=*) value="${line#E:ID_LOOP_BACKING_INODE=}" (( back_inode_count += 1 )) back_inode="$value" ;; E:ID_LOOP_BACKING_FILENAME_ENC=*) value="${line#E:ID_LOOP_BACKING_FILENAME_ENC=}" (( encoded_filename_count += 1 )) encoded_filename="$value" ;; S:disk/by-diskseq/*) value="${line#S:disk/by-diskseq/}" (( cached_diskseq_count += 1 )) cached_diskseq="$value" ;; esac done <"$udev_path" || return 1 for field_name in back_major_minor back_inode encoded_filename cached_diskseq; do [[ -n "${!field_name}" ]] || return 1 done (( back_major_minor_count == 1 && back_inode_count == 1 && encoded_filename_count == 1 && cached_diskseq_count == 1 )) || return 1 [[ "$back_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1 [[ "$back_inode" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$encoded_filename" =~ ^([a-zA-Z0-9._/:+-]|\\x[0-9a-fA-F]{2})+$ ]] || return 1 [[ "$cached_diskseq" == "$diskseq_before" ]] || return 1 task5b_read_exact_single_line loop_major_minor_after "${sysfs_root}/dev" || return 1 task5b_read_exact_single_line diskseq_after "${sysfs_root}/diskseq" || return 1 task5b_read_exact_single_line backing_after \ "${sysfs_root}/loop/backing_file" || return 1 task5b_read_exact_single_line offset_after "${sysfs_root}/loop/offset" || return 1 task5b_read_exact_single_line sizelimit_after \ "${sysfs_root}/loop/sizelimit" || return 1 [[ "$loop_major_minor_after" == "$loop_major_minor_before" ]] || return 1 [[ "$diskseq_after" == "$diskseq_before" ]] || return 1 [[ "$backing_after" == "$backing_before" ]] || return 1 [[ "$offset_after" == "$offset_before" ]] || return 1 [[ "$sizelimit_after" == "$sizelimit_before" ]] || return 1 if task5b_loop_backing_is_recovery "$backing_before"; then [[ "$back_major_minor" == "$recovery_major_minor" ]] || return 1 fi printf -v "$destination_name" '%s' \ "/dev/${loop_name}|${back_inode}|${back_major_minor}|${loop_major_minor_before}|${offset_before}|${sizelimit_before}|${diskseq_before}|${cached_diskseq}|${diskseq_after}|${encoded_filename}" } task5b_recovery_loop_count() { local destination_name="${1-}" recovery_major_minor='' local loop_path loop_name identity_line identity_record='' loop_index local -a before_paths=() after_paths=() (( $# == 1 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 task5b_read_exact_single_line recovery_major_minor /sys/class/block/sda3/dev || return 1 [[ "$recovery_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1 shopt -s nullglob before_paths=(/sys/class/block/loop*/loop/backing_file) for loop_path in "${before_paths[@]}"; do loop_name="${loop_path#/sys/class/block/}" loop_name="${loop_name%%/*}" task5b_loop_live_identity_line "$loop_name" "$recovery_major_minor" \ identity_line || return 1 identity_record+="$identity_line"$'\n' done after_paths=(/sys/class/block/loop*/loop/backing_file) shopt -u nullglob (( ${#after_paths[@]} == ${#before_paths[@]} )) || return 1 for (( loop_index=0; loop_index<${#before_paths[@]}; loop_index++ )); do [[ "${after_paths[loop_index]}" == "${before_paths[loop_index]}" ]] || return 1 done task5b_loop_identity_count_from_record "$identity_record" \ "$recovery_major_minor" "$destination_name" } task5b_path_state_from_observation() { local destination_name="${1-}" exists="${2-}" symlink="${3-}" state=absent (( $# == 3 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 [[ "$exists" == 0 || "$exists" == 1 ]] || return 1 [[ "$symlink" == 0 || "$symlink" == 1 ]] || return 1 (( exists == 0 && symlink == 0 )) || state=present printf -v "$destination_name" '%s' "$state" } task5b_path_state() { local destination_name="${1-}" path="${2-}" exists=0 symlink=0 (( $# == 2 )) || return 1 [[ -e "$path" ]] && exists=1 [[ -L "$path" ]] && symlink=1 task5b_path_state_from_observation "$destination_name" "$exists" "$symlink" } task5b_fixture_process_argv_matches() { local current_pid="${1-}" candidate_pid="${2-}" executable='' argument local test_script_match=0 fixture_marker_match=0 shift 2 || return 1 [[ "$current_pid" =~ ^[1-9][0-9]*$ && "$candidate_pid" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$candidate_pid" != "$current_pid" ]] || return 1 (( $# >= 1 )) || return 1 executable="$1" [[ "$executable" == bash || "$executable" == /bin/bash || "$executable" == /usr/bin/bash ]] || return 1 for argument in "$@"; do case "$argument" in "$REPOSITORY_ROOT/scripts/validate/test-k3s-local-recovery.sh"|\ scripts/validate/test-k3s-local-recovery.sh) test_script_match=1 ;; task5b-fixture|task5b-fixture:*) fixture_marker_match=1 ;; esac done (( test_script_match == 1 || fixture_marker_match == 1 )) } task5b_expected_safe_live_state_record() { local destination_name="${1-}" (( $# == 1 )) || return 1 [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 printf -v "$destination_name" '%s' \ $'mounts_sda3=0\nmounts_outer=0\nmounts_inner=0\nouter_mount_path=absent\ninner_mount_path=absent\nrecovery_loops=0\nmapper_main=absent\nmapper_proof=absent\nreserved_root=absent\ntask5b_temp_residue=0\ntask5b_fixture_processes=0' } task5b_live_state_record() { local destination_name="${1-}" line mountinfo_record='' local mounts_sda3=0 mounts_outer=0 mounts_inner=0 recovery_loops=0 local outer_mount_path=absent inner_mount_path=absent local mapper_main=absent mapper_proof=absent reserved_root=absent local task5b_temp_residue=0 task5b_fixture_processes=0 pid_path candidate_pid local -a residue_paths=() process_arguments=() [[ "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 while IFS= read -r line; do mountinfo_record+="$line"$'\n' done /dev/null || : task5b_fixture_process_argv_matches "$BASHPID" "$candidate_pid" \ "${process_arguments[@]}" || continue (( task5b_fixture_processes += 1 )) done printf -v "$destination_name" \ 'mounts_sda3=%s\nmounts_outer=%s\nmounts_inner=%s\nouter_mount_path=%s\ninner_mount_path=%s\nrecovery_loops=%s\nmapper_main=%s\nmapper_proof=%s\nreserved_root=%s\ntask5b_temp_residue=%s\ntask5b_fixture_processes=%s' \ "$mounts_sda3" "$mounts_outer" "$mounts_inner" "$outer_mount_path" \ "$inner_mount_path" "$recovery_loops" "$mapper_main" "$mapper_proof" \ "$reserved_root" "$task5b_temp_residue" "$task5b_fixture_processes" } task5b_gate0_review_round1_adversarial_assertions() { local mount_record mounts_sda3=0 mounts_outer=0 mounts_inner=0 path_state='' local selector_output='' selector_rc=0 mount_record=$'36 25 8:3 / /mnt/k3s-recovery-ssd rw - ntfs3 /dev/sda3 rw\n37 25 253:0 / /srv/recovery/k3s rw - ext4 /dev/mapper/k3s-recovery rw\n' assert_succeeds task5b_mountinfo_counts_record "$mount_record" mounts_sda3 \ mounts_outer mounts_inner assert_eq 1 "$mounts_sda3" 'collector parses exact /dev/sda3 source' assert_eq 1 "$mounts_outer" 'collector parses exact outer mountpoint' assert_eq 1 "$mounts_inner" 'collector parses exact inner mountpoint' assert_succeeds task5b_loop_backing_is_recovery /fixture/recovery-partition assert_succeeds task5b_loop_backing_is_recovery \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks assert_fails task5b_loop_backing_is_recovery /dev/sda3 assert_fails task5b_loop_backing_is_recovery /other/recovery-partition assert_fails task5b_loop_backing_is_recovery \ /mnt/k3s-recovery-ssd/HyeonworksRecovery/containers/k3s-recovery.luks.bak assert_succeeds task5b_path_state_from_observation path_state 0 0 assert_eq absent "$path_state" 'collector treats only nonexistence and nonsymlink as absent' assert_succeeds task5b_path_state_from_observation path_state 0 1 assert_eq present "$path_state" 'collector treats a dangling symlink as present' assert_succeeds task5b_path_state_from_observation path_state 1 0 assert_eq present "$path_state" 'collector treats an existing path as present' assert_fails task5b_fixture_process_argv_matches 100 100 /usr/bin/bash \ scripts/validate/test-k3s-local-recovery.sh assert_succeeds task5b_fixture_process_argv_matches 100 101 /usr/bin/bash \ scripts/validate/test-k3s-local-recovery.sh assert_succeeds task5b_fixture_process_argv_matches 100 102 /bin/bash -c : \ task5b-fixture:gate0 assert_fails task5b_fixture_process_argv_matches 100 103 /usr/bin/bash -c : assert_fails task5b_fixture_process_argv_matches 100 104 /usr/bin/sleep 10 \ task5b-fixture:gate0 selector_output="$( TASK5B_FOCUS=not-a-task5b-focus TASK5A_FOCUS=dispatcher \ /usr/bin/timeout 10s /usr/bin/bash "${BASH_SOURCE[0]}" 2>&1 )" || selector_rc=$? assert_eq 2 "$selector_rc" 'unknown Task 5B focus is rejected before legacy selection' assert_eq 'TEST FAILURE: unsupported TASK5B_FOCUS: not-a-task5b-focus' \ "$selector_output" 'unknown Task 5B focus emits only the early selector diagnostic' } task5b_gate0_review_round2_adversarial_assertions() { local record='' match_count=0 mutated_helper_body='' fuse_body='' record='/dev/loop37|4242|8:3|7:37|0|0|99|99|99|\x2ffixture\x2frecovery.luks' assert_succeeds task5b_loop_identity_count_from_record "$record" 8:3 \ match_count assert_eq 1 "$match_count" 'loop identity parser counts exact backing MAJ:MIN' record='/dev/loop37|4242|8:33|7:37|0|0|99|99|99|\x2froot\x2fsnap' assert_succeeds task5b_loop_identity_count_from_record "$record" 8:3 \ match_count assert_eq 0 "$match_count" 'loop identity parser rejects a nonmatching device' record='/dev/loop37|4242|8:3|7:37|0|0|99|99|99|\x2fdeleted\x20backing' assert_succeeds task5b_loop_identity_count_from_record "$record" 8:3 \ match_count assert_eq 1 "$match_count" \ 'deleted or missing backing pathname cannot hide matching device identity' assert_fails task5b_loop_identity_count_from_record \ '/dev/loop37|4242|8:3|7:37|0|0|99|99' 8:3 match_count assert_fails task5b_loop_identity_count_from_record \ '/dev/loop37|4242|8:3|7:37|0|0|99|98|99|\x2fstale' 8:3 match_count record=$'/dev/loop37|4242|8:3|7:37|0|0|99|99|99|\\x2fone\n/dev/loop37|4243|8:33|7:37|0|0|99|99|99|\\x2ftwo' assert_fails task5b_loop_identity_count_from_record "$record" 8:3 match_count mutated_helper_body="$(declare -f task5b_gate0_assert_rejected)"$'\n'\ 'trap - DEBUG /usr/bin/printf TASK5B_TRAP_DISABLE_CANARY' || return 1 assert_fails task5b_gate0_static_body_is_closed "$mutated_helper_body" fuse_body="$(declare -f task5b_gate0_real_exec_fuse)" || return 1 [[ "$fuse_body" != *'|trap)'* ]] || fail 'active fuse must not allowlist DEBUG-trap teardown' } task5b_api_state_trimmed_line_count() { local body="${1-}" expected_line="${2-}" line count=0 (( $# == 2 )) || return 1 while IFS= read -r line; do line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" [[ "$line" != "$expected_line" ]] || (( count += 1 )) done <<<"$body" printf '%s\n' "$count" } task5b_api_state_dispatcher_has_obsolete_provider_flow() { local body="${1-}" line local provider_token_re='("\$header_provider"|\$header_provider|"\$\{header_provider\}"|\$\{header_provider\})' local direct_start_re='' control_provider_re='' separator_provider_re='' (( $# == 1 )) || return 1 direct_start_re="^[[:space:]]*${provider_token_re}([[:space:];|&]|$)" control_provider_re="(^|[[:space:]])(if|then|elif|while|until|!)[[:space:]]+${provider_token_re}([[:space:];|&]|$)" separator_provider_re="(&&|\|\||;)[[:space:]]*${provider_token_re}([[:space:];|&]|$)" while IFS= read -r line; do line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" [[ "$line" != *'${@:3}'* && "$line" != *provider_arguments* ]] || return 0 [[ ! "$line" =~ $direct_start_re && ! "$line" =~ $control_provider_re && ! "$line" =~ $separator_provider_re ]] || return 0 if [[ "$line" == *'$header_provider'* || "$line" == *'${header_provider}'* ]]; then case "$line" in action_arguments=*|action_arguments+=*|_k3slr_lifecycle_action[[:space:]]*) return 0 ;; esac [[ "$line" =~ ^[[:alnum:]_]*arguments(\+)?= ]] && return 0 fi done <<<"$body" return 1 } # The production breaks this catches are the obsolete caller-provider argv # forwarding API, a prepare action that still accepts two arguments, or state # created anywhere except one empty dispatcher-owned associative declaration. # The production dispatcher/action are observed directly and are never # replaced by a fixture success stub. task5b_api_state_contract() ( local dispatch_body='' action_body='' main_body='' library_source='' local state_declaration_count=0 state_append_count=0 action_call_count=0 local main_dispatch_count=0 state_reference_count=0 local hook_calls=0 action_calls=0 hook_calls_exact=0 action_calls_exact=0 local command_calls=0 command_calls_exact=0 command_calls_direct=0 local tty_calls=0 provider_calls=0 action_rc=0 local dispatch_exact_actual_exec=0 command_direct_actual_exec=0 local main_arity_actual_exec=0 wrong_action_actual_exec=0 local dispatch_exact_rc=0 dispatch_invalid_rc=0 dispatch_case='' index=0 local main_wrong_arity_successes=0 main_contract_sentinel=not-loaded local probe_rc=0 record='' record_tag='' record_profile='' record_extra='' local caller_value='' local -a invalid_dispatch_cases=(zero one prepare-third open-third close-four) local -a invalid_dispatch_rcs=() invalid_dispatch_hook_calls=() local -a invalid_dispatch_action_calls=() invalid_dispatch_command_calls=() local -a invalid_dispatch_actual_exec=() local -a wrong_action_rcs=() record_fields=() local -A caller_seed=([caller_provider_argument]=must-not-be-read) # shellcheck source=/dev/null source "$LIBRARY_PATH" _k3slr_command() { (( command_calls += 1 )); return 97; } _k3slr_tty_capability() { (( tty_calls += 1 )); return 1; } _k3slr_read_tty_line() { (( tty_calls += 1 )); return 1; } task5b_api_state_header_provider() { (( provider_calls += 1 )); return 97; } task5b_api_state_debug_observer() { local observed_command="${1-}" (( $# == 1 )) || return 1 case "$observed_command" in _k3slr_lifecycle_step\ *) (( hook_calls += 1 )) ;; _k3slr_lifecycle_action\ *) (( action_calls += 1 )) ;; esac } task5b_api_dispatch_probe_payload() { local dispatch_case="${1-}" dispatch_rc=0 hook_calls=0 action_calls=0 local command_calls=0 provider_calls=0 local -A caller_seed=([caller_provider_argument]=must-not-be-read) (( $# == 1 )) || return 1 case "$dispatch_case" in exact) _k3slr_lifecycle_dispatch prepare task5b_api_state_header_provider || dispatch_rc=$? ;; zero) _k3slr_lifecycle_dispatch || dispatch_rc=$? ;; one) _k3slr_lifecycle_dispatch prepare || dispatch_rc=$? ;; prepare-third) _k3slr_lifecycle_dispatch prepare task5b_api_state_header_provider \ caller_seed || dispatch_rc=$? ;; open-third) _k3slr_lifecycle_dispatch open task5b_api_state_header_provider \ caller_seed || dispatch_rc=$? ;; close-four) _k3slr_lifecycle_dispatch close task5b_api_state_header_provider \ caller_seed extra || dispatch_rc=$? ;; *) return 1 ;; esac task5b_production_probe_emit_ok api_dispatch "$dispatch_rc" "$hook_calls" \ "$action_calls" "$command_calls" "$provider_calls" \ "${caller_seed[caller_provider_argument]}" } task5b_api_direct_probe_payload() { local direct_case="${1-}" action_rc=0 command_calls=0 local -a wrong_action_rcs=() (( $# == 1 )) || return 1 case "$direct_case" in exact) _k3slr_lifecycle_action prepare sudo _k3slr_prepare_invocation || action_rc=$? task5b_production_probe_emit_ok api_direct "$action_rc" "$command_calls" ;; wrong-arities) _k3slr_lifecycle_action || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action prepare || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action prepare sudo || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action prepare sudo _k3slr_prepare_invocation extra || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action open || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action open sudo extra || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action close || action_rc=$? wrong_action_rcs+=("$action_rc") action_rc=0 _k3slr_lifecycle_action close sudo extra || action_rc=$? wrong_action_rcs+=("$action_rc") task5b_production_probe_emit_ok api_direct "$command_calls" \ "${wrong_action_rcs[@]}" ;; *) return 1 ;; esac } task5b_api_main_arity_probe_payload() { local main_wrong_arity_successes=0 (( $# == 0 )) || return 1 _k3slr_lifecycle_main && (( main_wrong_arity_successes += 1 )) _k3slr_lifecycle_main prepare && (( main_wrong_arity_successes += 1 )) _k3slr_lifecycle_main prepare execute caller_seed && (( main_wrong_arity_successes += 1 )) task5b_production_probe_emit_ok api_main_arity \ "$main_wrong_arity_successes" "$K3SLR_SCHEMA_VERSION" } task5b_production_probe_adversary_matrix || return 1 task5b_production_probe_static_graph_is_closed api_dispatch || return 1 task5b_production_probe_static_graph_is_closed api_direct || return 1 task5b_production_probe_static_graph_is_closed api_main_arity || return 1 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm api_dispatch || return 1 task5b_api_dispatch_probe_payload exact )" || probe_rc=$? assert_eq 0 "$probe_rc" 'exact dispatcher probe completes without a fuse block' IFS='|' read -r record_tag record_profile dispatch_exact_rc hook_calls_exact \ action_calls_exact command_calls_exact provider_calls caller_value \ record_extra <<<"$record" || return 1 [[ "$record_tag" == TASK5B_PROBE_OK && "$record_profile" == api_dispatch && -z "$record_extra" ]] || return 1 assert_eq 0 "$provider_calls" 'exact dispatcher does not call its provider' assert_eq must-not-be-read "$caller_value" \ 'exact dispatcher leaves caller-owned state unchanged' for dispatch_case in "${invalid_dispatch_cases[@]}"; do record='' probe_rc=0 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm api_dispatch || return 1 task5b_api_dispatch_probe_payload "$dispatch_case" )" || probe_rc=$? assert_eq 0 "$probe_rc" \ "wrong dispatcher arity probe completes without a fuse block: $dispatch_case" IFS='|' read -r record_tag record_profile dispatch_invalid_rc hook_calls \ action_calls command_calls provider_calls caller_value record_extra \ <<<"$record" || return 1 [[ "$record_tag" == TASK5B_PROBE_OK && "$record_profile" == api_dispatch && -z "$record_extra" ]] || return 1 invalid_dispatch_rcs+=("$dispatch_invalid_rc") invalid_dispatch_hook_calls+=("$hook_calls") invalid_dispatch_action_calls+=("$action_calls") invalid_dispatch_command_calls+=("$command_calls") invalid_dispatch_actual_exec+=(0) assert_eq 0 "$provider_calls" \ "wrong dispatcher arity calls no provider: $dispatch_case" assert_eq must-not-be-read "$caller_value" \ "wrong dispatcher arity leaves caller state unchanged: $dispatch_case" done record='' probe_rc=0 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm api_direct || return 1 task5b_api_direct_probe_payload exact )" || probe_rc=$? assert_eq 0 "$probe_rc" 'exact direct Action 1 probe completes without a fuse block' IFS='|' read -r record_tag record_profile action_rc command_calls_direct \ record_extra <<<"$record" || return 1 [[ "$record_tag" == TASK5B_PROBE_OK && "$record_profile" == api_direct && -z "$record_extra" ]] || return 1 K3SLR_WRAPPER_CONTRACT="$CONTRACT_PATH" K3SLR_SCHEMA_VERSION="$main_contract_sentinel" record='' probe_rc=0 record="$( K3SLR_WRAPPER_CONTRACT="$CONTRACT_PATH" K3SLR_SCHEMA_VERSION="$main_contract_sentinel" exec 9>&1 exec 2>&1 task5b_production_probe_arm api_main_arity || return 1 task5b_api_main_arity_probe_payload )" || probe_rc=$? assert_eq 0 "$probe_rc" 'wrong main arity probe completes without a fuse block' IFS='|' read -r record_tag record_profile main_wrong_arity_successes \ K3SLR_SCHEMA_VERSION record_extra <<<"$record" || return 1 [[ "$record_tag" == TASK5B_PROBE_OK && "$record_profile" == api_main_arity && -z "$record_extra" ]] || return 1 dispatch_body="$(declare -f _k3slr_lifecycle_dispatch)" || return 1 action_body="$(declare -f _k3slr_lifecycle_action)" || return 1 main_body="$(declare -f _k3slr_lifecycle_main)" || return 1 library_source="$(<"$LIBRARY_PATH")" || return 1 state_declaration_count="$(task5b_api_state_trimmed_line_count "$dispatch_body" \ 'local -A _k3slr_prepare_invocation=();')" || return 1 state_append_count="$(task5b_api_state_trimmed_line_count "$dispatch_body" \ 'action_arguments+=(_k3slr_prepare_invocation);')" || return 1 action_call_count="$(task5b_api_state_trimmed_line_count "$dispatch_body" \ '_k3slr_lifecycle_action "${action_arguments[@]}" || return 1;')" || return 1 main_dispatch_count="$(task5b_api_state_trimmed_line_count "$main_body" \ '_k3slr_lifecycle_dispatch "$lifecycle" _k3slr_prove_header_restore')" || return 1 while IFS= read -r line; do [[ "$line" != *'_k3slr_prepare_invocation'* ]] || (( state_reference_count += 1 )) done <<<"$library_source" (( hook_calls_exact > 0 )) || fail 'exact two-argument prepare dispatcher did not reach its production hook' (( action_calls_exact > 0 )) || fail 'exact two-argument prepare dispatcher did not reach its production action' (( dispatch_exact_rc != 0 )) || fail 'API/state checkpoint production dispatcher is not fail-closed' for index in "${!invalid_dispatch_cases[@]}"; do dispatch_case="${invalid_dispatch_cases[$index]}" (( invalid_dispatch_rcs[index] != 0 )) || fail "wrong dispatcher arity succeeded: $dispatch_case" assert_eq 0 "${invalid_dispatch_hook_calls[$index]}" \ "wrong dispatcher arity reached production hook: $dispatch_case" assert_eq 0 "${invalid_dispatch_action_calls[$index]}" \ "wrong dispatcher arity reached production action: $dispatch_case" assert_eq 0 "${invalid_dispatch_command_calls[$index]}" \ "wrong dispatcher arity reached the command seam: $dispatch_case" assert_eq 0 "${invalid_dispatch_actual_exec[$index]}" \ "wrong dispatcher arity executes no real command: $dispatch_case" done assert_eq must-not-be-read "${caller_seed[caller_provider_argument]}" \ 'rejected caller state/provider argument remains unchanged' assert_eq 1 "$command_calls_exact" \ 'exact dispatcher reaches the safe Action 1 command fuse once' assert_eq 1 "$command_calls_direct" \ 'exact direct Action 1 reaches the safe command fuse once' assert_eq 0 "$dispatch_exact_actual_exec" \ 'exact dispatcher executes no real command' assert_eq 0 "$command_direct_actual_exec" \ 'exact direct Action 1 executes no real command' assert_eq 0 "$main_arity_actual_exec" \ 'wrong main arity executes no real command' assert_eq 0 "$tty_calls" 'API/state arity matrix has TTY effect 0' assert_eq 0 "$provider_calls" 'API/state arity matrix has provider effect 0' assert_eq 97 "$action_rc" \ 'exact three-argument Action 1 preserves the safe command-fuse failure' assert_eq 0 "$main_wrong_arity_successes" \ 'main rejects zero, one, and three arguments' assert_eq not-loaded "$K3SLR_SCHEMA_VERSION" \ 'wrong main arity fails before contract load effects' assert_eq 1 "$state_declaration_count" \ 'dispatcher has one direct empty prepare associative-state declaration' assert_eq 1 "$state_append_count" \ 'dispatcher appends the fixed prepare-state name once to action argv' assert_eq 1 "$action_call_count" \ 'dispatcher has one production action invocation site' assert_eq 1 "$main_dispatch_count" \ 'main has one exact two-argument dispatcher invocation site' assert_eq 3 "$state_reference_count" \ 'production has only the prepare-state declaration, fixed action carrier, and Action 1 literal guard' assert_fails task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body" assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''${@:3}' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''provider_arguments=("$header_provider")' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''"$header_provider" restore' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''if "$header_provider"; then :; fi' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n'': && "$header_provider" restore' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''! "$header_provider" restore' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n'': || "$header_provider" restore' assert_succeeds task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''action_arguments+=("$header_provider")' assert_fails task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''[[ "$header_provider" == _k3slr_prove_header_restore ]] || return 1' assert_fails task5b_api_state_dispatcher_has_obsolete_provider_flow \ "$dispatch_body"$'\n''declare -F "$header_provider" >/dev/null || return 1' [[ "$dispatch_body" == *'header_provider="${2-}"'* ]] || fail 'dispatcher invocation-local provider identity binding is absent' [[ "$dispatch_body" != *'${@:3}'* && "$dispatch_body" != *provider_arguments* ]] || fail 'dispatcher retains obsolete caller provider argument compatibility' [[ "$dispatch_body" == *'(( $# == 2 )) || return 1'* ]] || fail 'dispatcher exact two-argument arity guard is absent' [[ "$action_body" == *'prepare)'*'(( $# == 3 )) || return 1'* && "$action_body" == *'open | close)'*'(( $# == 2 )) || return 1'* ]] || fail 'production action handler retains obsolete lifecycle arity' [[ "$main_body" != *'_k3slr_prepare_invocation'* && "$main_body" != *'${@:3}'* ]] || fail 'main creates or forwards prepare invocation state' [[ "$main_body" == *'(( $# == 2 )) || return 1'* ]] || fail 'main exact two-argument arity guard is absent' [[ "${main_body%%_k3slr_load_contract*}" == *'(( $# == 2 )) || return 1'* ]] || fail 'main arity guard does not precede contract effects' record='' probe_rc=0 record="$( exec 9>&1 exec 2>&1 task5b_production_probe_arm api_direct || return 1 task5b_api_direct_probe_payload wrong-arities )" || probe_rc=$? assert_eq 0 "$probe_rc" 'wrong action arity probe completes without a fuse block' IFS='|' read -r -a record_fields <<<"$record" || return 1 record_tag="${record_fields[0]-}" record_profile="${record_fields[1]-}" command_calls="${record_fields[2]-}" wrong_action_rcs=("${record_fields[@]:3}") [[ "$record_tag" == TASK5B_PROBE_OK && "$record_profile" == api_direct && ${#wrong_action_rcs[@]} == 8 ]] || return 1 for action_rc in "${wrong_action_rcs[@]}"; do (( action_rc != 0 )) || fail 'wrong action arity unexpectedly succeeded' done assert_eq 0 "$command_calls" 'wrong action/dispatcher arity has command effect 0' assert_eq 0 "$wrong_action_actual_exec" \ 'wrong action/dispatcher arity has no fuse block' assert_eq 0 "$tty_calls" 'wrong action/dispatcher arity has TTY effect 0' assert_eq 0 "$provider_calls" 'wrong action/dispatcher arity has provider effect 0' ) # The production breaks this catches are a high-level TTY gate that bypasses # the low capability seam, fails to preserve its status, or reaches it after a # wrong-arity call. Only the lowest seam is replaced in this source-loaded # foreground subshell. task5b_tty_seam_contract() ( local tty_capability_calls=0 tty_capability_rc=0 high_rc=0 # shellcheck source=/dev/null source "$LIBRARY_PATH" _k3slr_tty_capability() { (( tty_capability_calls += 1 )) return "$tty_capability_rc" } high_rc=0 _k3slr_require_execute_tty || high_rc=$? assert_eq 1 "$tty_capability_calls" \ 'TTY high helper delegates exactly once on low-capability success' assert_eq 0 "$high_rc" \ 'TTY high helper preserves low-capability success' tty_capability_calls=0 tty_capability_rc=73 high_rc=0 _k3slr_require_execute_tty || high_rc=$? assert_eq 1 "$tty_capability_calls" \ 'TTY high helper delegates exactly once on low-capability failure' assert_eq 73 "$high_rc" \ 'TTY high helper preserves low-capability failure' tty_capability_calls=0 tty_capability_rc=0 high_rc=0 _k3slr_require_execute_tty unexpected || high_rc=$? assert_eq 1 "$high_rc" 'TTY high helper rejects wrong arity' assert_eq 0 "$tty_capability_calls" \ 'TTY high helper rejects wrong arity before the low seam' ) task5b_action1_literal_line_count() { local body="${1-}" needle="${2-}" destination_name="${3-}" line count=0 (( $# == 3 )) || return 1 [[ -n "$needle" && "$destination_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 1 while IFS= read -r line; do [[ "$line" != *"$needle"* ]] || (( count += 1 )) done <<<"$body" printf -v "$destination_name" '%s' "$count" } # Every literal direct prepare/sudo test call must live in a fixture whose # command seam fails closed or in the Action 1 exact matcher. The historical # unavailable-action loop is indirect, so it has its own command fuse and must # no longer enumerate the now-available prepare/sudo action. task5b_action1_nonfocused_direct_sudo_paths_are_fused() { local direct_needle='' test_source='' safe_bodies='' local dryrun_body='' api_state_body='' action1_body='' unavailable_body='' local source_count=0 safe_count=0 direct_needle='_k3slr_lifecycle_action prepare '"sudo" test_source="$(<"${BASH_SOURCE[0]}")" || return 1 dryrun_body="$(declare -f task5a_dry_run_and_prepackage_action_binding)" || return 1 api_state_body="$(declare -f task5b_api_state_contract)" || return 1 action1_body="$(declare -f task5b_action1_command_binding_contract)" || return 1 unavailable_body="$(declare -f task5a_production_actions_fail_closed)" || return 1 safe_bodies="$dryrun_body"$'\n'"$api_state_body"$'\n'"$action1_body" task5b_action1_literal_line_count "$test_source" "$direct_needle" \ source_count || return 1 task5b_action1_literal_line_count "$safe_bodies" "$direct_needle" \ safe_count || return 1 (( source_count > 0 && source_count == safe_count )) || return 1 [[ "$dryrun_body" == *'_k3slr_command ()'* && "$dryrun_body" == *'return 97'* && "$dryrun_body" == *'task5b_production_probe_arm legacy_binding'* ]] || return 1 [[ "$api_state_body" == *'_k3slr_command ()'* && "$api_state_body" == *'return 97'* && "$api_state_body" == *'task5b_production_probe_arm api_direct'* ]] || return 1 [[ "$action1_body" == *'task5b_gate0_route_equals sudo-validate "$@"'* && "$action1_body" == *'return 97'* && "$action1_body" == *'task5b_production_probe_arm action1_main'* && "$action1_body" == *'task5b_production_probe_arm action1_alternate'* ]] || return 1 [[ "$unavailable_body" == *'_k3slr_command ()'* && "$unavailable_body" == *'return 97'* && "$unavailable_body" == *'task5b_production_probe_arm unavailable'* && "$unavailable_body" != *'prepare_unavailable=(sudo'* ]] || return 1 } # The production breaks this catches are a prepare/sudo handler that omits or # misframes the exact sudo validation argv, reaches a command after sudo, calls # the header provider early, or accepts any state name except the dispatcher- # owned literal. The real main, dispatcher, hook, action, and invocation state # remain untouched; only the lowest TTY and command seams are replaced. task5b_action1_command_binding_contract() ( local tty_calls=0 tty_rc=0 sudo_dispatches=0 sudo_rc=0 local unexpected_commands=0 actual_exec=0 provider_calls=0 main_rc=0 local hook_sudo_entries=0 hook_context_entries=0 hook_suffix_entries=0 local action_sudo_entries=0 action_context_entries=0 action_suffix_entries=0 local action_rc=0 command_body='' local probe_record='' record_tag='' record_profile='' record_extra='' # shellcheck source=/dev/null source "$LIBRARY_PATH" K3SLR_WRAPPER_CONTRACT="$CONTRACT_PATH" _k3slr_prove_header_restore() { (( provider_calls += 1 )) return 97 } _k3slr_tty_capability() { (( tty_calls += 1 )) return "$tty_rc" } _k3slr_command() { if task5b_gate0_route_equals sudo-validate "$@"; then (( sudo_dispatches += 1 )) return "$sudo_rc" fi (( unexpected_commands += 1 )) return 97 } task5b_action1_debug_observer() { local observed_command="${1-}" observed_step="${2-}" (( $# == 2 )) || return 1 case "$observed_command" in _k3slr_lifecycle_step\ *) case "$observed_step" in sudo) (( hook_sudo_entries += 1 )) ;; context) (( hook_context_entries += 1 )) ;; *) (( hook_suffix_entries += 1 )) ;; esac ;; _k3slr_lifecycle_action\ *) case "$observed_step" in sudo) (( action_sudo_entries += 1 )) ;; context) (( action_context_entries += 1 )) ;; *) (( action_suffix_entries += 1 )) ;; esac ;; esac } task5b_action1_main_probe_payload() { local probe_rc=0 tty_calls=0 sudo_dispatches=0 unexpected_commands=0 local provider_calls=0 hook_sudo_entries=0 hook_context_entries=0 local hook_suffix_entries=0 action_sudo_entries=0 local action_context_entries=0 action_suffix_entries=0 (( $# == 0 )) || return 1 _k3slr_lifecycle_main prepare execute || probe_rc=$? task5b_production_probe_emit_ok action1_main "$probe_rc" "$tty_calls" \ "$sudo_dispatches" "$unexpected_commands" "$provider_calls" \ "$hook_sudo_entries" "$hook_context_entries" "$hook_suffix_entries" \ "$action_sudo_entries" "$action_context_entries" \ "$action_suffix_entries" } task5b_action1_alternate_probe_payload() { local probe_rc=0 tty_calls=0 sudo_dispatches=0 unexpected_commands=0 local provider_calls=0 hook_sudo_entries=0 hook_context_entries=0 local hook_suffix_entries=0 action_sudo_entries=0 local action_context_entries=0 action_suffix_entries=0 (( $# == 0 )) || return 1 _k3slr_lifecycle_action prepare sudo task5b_alternate_state || probe_rc=$? task5b_production_probe_emit_ok action1_alternate "$probe_rc" \ "$tty_calls" "$sudo_dispatches" "$unexpected_commands" \ "$provider_calls" "$hook_sudo_entries" "$hook_context_entries" \ "$hook_suffix_entries" "$action_sudo_entries" \ "$action_context_entries" "$action_suffix_entries" } task5b_action1_run_probe() { local probe_kind="${1-}" probe_profile='' local guarded_rc=0 (( $# == 1 )) || return 1 case "$probe_kind" in main) probe_profile=action1_main ;; alternate) probe_profile=action1_alternate ;; *) return 1 ;; esac case "$probe_kind" in main) probe_record="$( local task5b_probe_profile='' exec 9>&1 exec 2>&1 task5b_production_probe_arm action1_main || return 1 task5b_action1_main_probe_payload )" || guarded_rc=$? ;; alternate) probe_record="$( local task5b_probe_profile='' exec 9>&1 exec 2>&1 task5b_production_probe_arm action1_alternate || return 1 task5b_action1_alternate_probe_payload )" || guarded_rc=$? ;; esac if (( guarded_rc != 0 )); then return "$guarded_rc" fi record_tag='' record_extra='' IFS='|' read -r record_tag record_profile action_rc tty_calls \ sudo_dispatches unexpected_commands provider_calls hook_sudo_entries \ hook_context_entries hook_suffix_entries action_sudo_entries \ action_context_entries action_suffix_entries record_extra \ <<<"$probe_record" || return 1 [[ "$record_tag" == TASK5B_PROBE_OK && "$record_profile" == "$probe_profile" && -z "$record_extra" ]] || fail "Action 1 $probe_kind model keeps combined stdout and stderr empty" actual_exec=0 } task5b_action1_run_main() { task5b_action1_run_probe main || return 1 main_rc="$action_rc" } task5b_production_probe_adversary_matrix || { printf 'TEST FAILURE: Action 1 production-probe adversary matrix failed\n' >&2 return 1 } task5b_production_probe_static_graph_is_closed action1_main || { printf 'TEST FAILURE: Action 1 main exact graph scan failed\n' >&2 return 1 } task5b_production_probe_static_graph_is_closed action1_alternate || { printf 'TEST FAILURE: Action 1 alternate exact graph scan failed\n' >&2 return 1 } task5b_action1_run_main || { printf 'TEST FAILURE: Action 1 main guarded probe did not emit an OK record\n' >&2 return 1 } (( main_rc != 0 )) || fail 'Action 1 success model stops at pending context' assert_eq 1 "$tty_calls" 'Action 1 full main reaches the low TTY seam once' (( action_sudo_entries > 0 )) || fail 'Action 1 full main reaches the production prepare/sudo handler' assert_eq 1 "$sudo_dispatches" \ 'Action 1 production handler dispatches exact sudo validation once' (( hook_sudo_entries > 0 )) || fail 'Action 1 success model records the sudo lifecycle hook entry' (( hook_context_entries > 0 )) || fail 'Action 1 success model reaches the pending context hook entry' (( action_context_entries > 0 )) || fail 'Action 1 success model reaches the pending context action entry' assert_eq 0 "$hook_suffix_entries" \ 'Action 1 success model reaches no hook after pending context' assert_eq 0 "$action_suffix_entries" \ 'Action 1 success model reaches no action after pending context' assert_eq 0 "$unexpected_commands" \ 'Action 1 success model dispatches no command after sudo validation' assert_eq 0 "$actual_exec" 'Action 1 success model executes no real command' assert_eq 0 "$provider_calls" 'Action 1 success model does not call the provider' command_body="$(declare -f _k3slr_command)" || return 1 assert_succeeds task5b_gate0_static_body_is_closed "$command_body" assert_succeeds task5b_action1_nonfocused_direct_sudo_paths_are_fused tty_calls=0 tty_rc=71 sudo_dispatches=0 sudo_rc=0 unexpected_commands=0 provider_calls=0 hook_sudo_entries=0 hook_context_entries=0 hook_suffix_entries=0 action_sudo_entries=0 action_context_entries=0 action_suffix_entries=0 task5b_action1_run_main (( main_rc != 0 )) || fail 'Action 1 rejects a failed low TTY capability' assert_eq 1 "$tty_calls" 'Action 1 TTY failure calls the low seam once' assert_eq 0 "$hook_sudo_entries" 'Action 1 TTY failure reaches no dispatcher hook' assert_eq 0 "$action_sudo_entries" 'Action 1 TTY failure reaches no action' assert_eq 0 "$sudo_dispatches" 'Action 1 TTY failure dispatches no sudo command' assert_eq 0 "$unexpected_commands" 'Action 1 TTY failure dispatches no command' assert_eq 0 "$actual_exec" 'Action 1 TTY failure executes no real command' assert_eq 0 "$provider_calls" 'Action 1 TTY failure calls no provider' tty_calls=0 tty_rc=0 sudo_dispatches=0 sudo_rc=97 unexpected_commands=0 provider_calls=0 hook_sudo_entries=0 hook_context_entries=0 hook_suffix_entries=0 action_sudo_entries=0 action_context_entries=0 action_suffix_entries=0 task5b_action1_run_main (( main_rc != 0 )) || fail 'Action 1 propagates modeled sudo failure' assert_eq 1 "$tty_calls" 'Action 1 sudo failure calls the low TTY seam once' (( hook_sudo_entries > 0 )) || fail 'Action 1 sudo failure reaches the sudo hook entry' (( action_sudo_entries > 0 )) || fail 'Action 1 sudo failure reaches the production sudo action entry' assert_eq 1 "$sudo_dispatches" \ 'Action 1 sudo failure reaches the exact sudo model once' assert_eq 0 "$hook_context_entries" \ 'Action 1 sudo failure reaches no context hook' assert_eq 0 "$action_context_entries" \ 'Action 1 sudo failure reaches no context action' assert_eq 0 "$hook_suffix_entries" 'Action 1 sudo failure reaches no suffix hook' assert_eq 0 "$action_suffix_entries" \ 'Action 1 sudo failure reaches no suffix action' assert_eq 0 "$unexpected_commands" \ 'Action 1 sudo failure dispatches no unexpected command' assert_eq 0 "$actual_exec" 'Action 1 sudo failure executes no real command' assert_eq 0 "$provider_calls" 'Action 1 sudo failure calls no provider' task5b_action1_run_probe alternate assert_eq 1 "$action_rc" 'Action 1 rejects an alternate exact-three state name' assert_eq 0 "$sudo_dispatches" \ 'Action 1 alternate state name dispatches no sudo command' assert_eq 0 "$unexpected_commands" \ 'Action 1 alternate state name dispatches no unexpected command' assert_eq 0 "$actual_exec" 'Action 1 alternate state executes no real command' assert_eq 0 "$provider_calls" 'Action 1 alternate state calls no provider' ) task5b_run_focus() { local focus="${1-}" expected_record before_record after_record focus_rc=0 (( $# == 1 )) || return 1 task5b_expected_safe_live_state_record expected_record || return 1 task5b_live_state_record before_record || return 1 assert_eq "$expected_record" "$before_record" \ "Task 5B $focus pre-snapshot is independently all-zero/all-absent" || return 1 task5b_gate0_contract || return 1 case "$focus" in gate0) task5b_gate0_review_round1_adversarial_assertions || focus_rc=$? if (( focus_rc == 0 )); then task5b_gate0_review_round2_adversarial_assertions || focus_rc=$? fi ;; api_state) task5b_api_state_contract || focus_rc=$? ;; tty_seam) task5b_tty_seam_contract || focus_rc=$? ;; action1) task5b_action1_command_binding_contract || focus_rc=$? ;; *) focus_rc=1 ;; esac task5b_gate0_contract || return 1 task5b_live_state_record after_record || return 1 assert_eq "$expected_record" "$after_record" \ "Task 5B $focus post-snapshot is independently all-zero/all-absent" || return 1 assert_eq "$before_record" "$after_record" \ "Task 5B $focus focus preserves promptless live state/process/temp record" || return 1 (( focus_rc == 0 )) || return "$focus_rc" } if [[ "${TASK5B_FOCUS:-}" == gate0 || "${TASK5B_FOCUS:-}" == api_state || "${TASK5B_FOCUS:-}" == tty_seam || "${TASK5B_FOCUS:-}" == action1 ]]; then assert_succeeds task5b_run_focus "$TASK5B_FOCUS" elif [[ "${TASK5A_FOCUS:-}" == wrappers ]]; then assert_succeeds task5a_wrapper_and_guard_contract elif [[ "${TASK5A_FOCUS:-}" == capabilities ]]; then assert_succeeds task5a_prepare_provider_capability_gate assert_succeeds task5a_open_close_capability_gate elif [[ "${TASK5A_FOCUS:-}" == dispatcher ]]; then assert_succeeds task5a_lifecycle_dispatch_contract assert_succeeds task5a_production_actions_fail_closed elif [[ "${TASK5A_FOCUS:-}" == records ]]; then assert_succeeds task5a_exact_record_and_pure_parser_contract elif [[ "${TASK5A_FOCUS:-}" == prepackage ]]; then assert_succeeds task5a_ntfs_capacity_and_prepackage_seams elif [[ "${TASK5A_FOCUS:-}" == capture ]]; then assert_succeeds task5a_exact_process_capture_contract elif [[ "${TASK5A_FOCUS:-}" == prepackage_system ]]; then assert_succeeds task5a_prepackage_system_command_matrix assert_succeeds task5a_ntfsinfo_pipeline_contract elif [[ "${TASK5A_FOCUS:-}" == dryrun ]]; then assert_succeeds task5a_dry_run_and_prepackage_action_binding elif [[ "${TASK4_REVIEW5_FOCUS:-}" == priority ]]; then assert_succeeds task4_review5_pending_priority elif [[ "${TASK4_REVIEW5_FOCUS:-}" == feas_cleanup ]]; then assert_succeeds task4_review5_feasibility_cleanup_failure_is_not_signal_success elif [[ "${TASK4_REVIEW5_FOCUS:-}" == unpinned ]]; then assert_succeeds task4_review5_published_unpinned_cleanup elif [[ "${TASK4_REVIEW5_FOCUS:-}" == parser ]]; then assert_succeeds task4_review5_exact_proc_record_contract elif [[ "${TASK4_REVIEW5_FOCUS:-}" == anchor ]]; then assert_succeeds task4_review5_default_supervisor_anchor assert_succeeds task4_review5_feasibility_default_supervisor_anchor elif [[ "${TASK4_REVIEW5_FOCUS:-}" == phase_fixture_cleanup ]]; then assert_succeeds task4_review5_phase_fixture_failure_cleanup_contract elif [[ "${TASK4_REVIEW4_FOCUS:-}" == phases ]]; then assert_succeeds task4_review4_signal_phase_matrix elif [[ "${TASK4_REVIEW4_FOCUS:-}" == boundaries ]]; then assert_succeeds task4_review4_stale_cached_group_boundary assert_succeeds task4_review4_direct_fallback_boundaries assert_succeeds task4_review4_term_ignoring_group_escalates assert_succeeds task4_review4_wait_clear_is_atomic elif [[ "${TASK4_REVIEW4_FOCUS:-}" == stale_group ]]; then assert_succeeds task4_review4_stale_cached_group_boundary elif [[ "${TASK4_REVIEW3_FOCUS:-}" == signal ]]; then assert_succeeds task4_review3_nested_pipeline_signal_cleanup elif [[ "${TASK4_REVIEW3_FOCUS:-}" == early_signal ]]; then assert_succeeds task4_review3_launch_window_signal_cleanup elif [[ "${TASK4_REVIEW2_FOCUS:-}" == critical ]]; then assert_succeeds task4_review2_tty_and_bounded_validated_pipe elif [[ "${TASK4_REVIEW2_FOCUS:-}" == show_protected ]]; then assert_succeeds task4_review2_show_protected_and_readonly elif [[ "${TASK4_REVIEW2_FOCUS:-}" == feas_show_protected ]]; then assert_succeeds task4_review2_feasibility_show_protected elif [[ "${TASK4_REVIEW2_FOCUS:-}" == feas_attachment_listing ]]; then assert_succeeds task4_review2_feasibility_attachment_listing assert_succeeds task4_review2_attachment_parser_contract elif [[ "${TASK4_REVIEW2_FOCUS:-}" == capture ]]; then assert_succeeds task4_review2_capture_one_line_exact_record elif [[ "${TASK4_REVIEW2_FOCUS:-}" == atomic ]]; then assert_succeeds task4_review2_atomic_save_transition elif [[ "${TASK4_REVIEW1_FOCUS:-}" == critical ]]; then assert_succeeds task4_review1_producer_must_finish_before_consumer elif [[ "${TASK4_REVIEW1_FOCUS:-}" == lineage ]]; then assert_succeeds task4_review1_preflight_and_lineage_gate elif [[ "${TASK4_REVIEW1_FOCUS:-}" == constants_lf ]]; then assert_succeeds task4_review1_exact_constants_become_readonly assert_succeeds task4_review1_generated_password_exact_lf elif [[ "${TASK4_REVIEW1_FOCUS:-}" == signal ]]; then assert_succeeds task4_review1_blocking_child_is_reaped elif [[ "${TASK4_REVIEW1_FOCUS:-}" == noclobber ]]; then assert_succeeds task4_review1_noclobber_lifecycle elif [[ "${TASK4_REVIEW1_FOCUS:-}" == parser ]]; then assert_succeeds task4_review1_raw_package_record elif [[ "${TASK4_REVIEW1_FOCUS:-}" == leak ]]; then assert_succeeds task4_review1_two_sentinel_leak_contract else assert_succeeds task5a_wrapper_and_guard_contract assert_succeeds task5a_prepare_provider_capability_gate assert_succeeds task5a_open_close_capability_gate assert_succeeds task5a_lifecycle_dispatch_contract assert_succeeds task5a_production_actions_fail_closed assert_succeeds task5a_exact_record_and_pure_parser_contract assert_succeeds task5a_ntfs_capacity_and_prepackage_seams assert_succeeds task5a_exact_process_capture_contract assert_succeeds task5a_prepackage_system_command_matrix assert_succeeds task5a_ntfsinfo_pipeline_contract assert_succeeds task5a_dry_run_and_prepackage_action_binding assert_succeeds task4_pipeline_contract assert_succeeds task4_tty_and_secret_api_contract assert_succeeds task4_feasibility_cleanup_and_leak_contract assert_succeeds task4_review1_producer_must_finish_before_consumer assert_succeeds task4_review1_preflight_and_lineage_gate assert_succeeds task4_review1_exact_constants_become_readonly assert_succeeds task4_review1_generated_password_exact_lf assert_succeeds task4_review1_blocking_child_is_reaped assert_succeeds task4_review1_noclobber_lifecycle assert_succeeds task4_review1_raw_package_record assert_succeeds task4_review1_two_sentinel_leak_contract assert_succeeds task4_review2_tty_and_bounded_validated_pipe assert_succeeds task4_review2_show_protected_and_readonly assert_succeeds task4_review2_feasibility_show_protected assert_succeeds task4_review2_feasibility_attachment_listing assert_succeeds task4_review2_attachment_parser_contract assert_succeeds task4_review2_capture_one_line_exact_record assert_succeeds task4_review2_atomic_save_transition assert_succeeds task4_review3_nested_pipeline_signal_cleanup assert_succeeds task4_review3_launch_window_signal_cleanup assert_succeeds task4_review4_stale_cached_group_boundary assert_succeeds task4_review4_direct_fallback_boundaries assert_succeeds task4_review4_term_ignoring_group_escalates assert_succeeds task4_review4_wait_clear_is_atomic assert_succeeds task4_review4_signal_phase_matrix assert_succeeds task4_review5_exact_proc_record_contract assert_succeeds task4_review5_default_supervisor_anchor assert_succeeds task4_review5_feasibility_default_supervisor_anchor assert_succeeds task4_review5_phase_fixture_failure_cleanup_contract assert_succeeds task4_review5_published_unpinned_cleanup assert_succeeds task4_review5_pending_priority assert_succeeds task4_review5_feasibility_cleanup_failure_is_not_signal_success fi printf 'PASS: local recovery contract, capacity, and validator fixtures\n'