fix: validate explicit attestation targets

This commit is contained in:
OthmanAdi
2026-09-27 04:42:31 +02:00
committed by Ahmad Othman Ammar Adi
parent fca9b04f95
commit 683d591a79
17 changed files with 709 additions and 244 deletions
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
+10 -4
View File
@@ -37,10 +37,16 @@ file. It does not create the legacy `.plan-attestation` file beside a slug plan.
does not resolve, the helper exits with an error instead of attesting a different
plan through the current-directory fallback.
`--target root` explicitly attests the legacy `./task_plan.md`, even while a
named plan is active. `--target <plan-id>` explicitly attests that named plan.
Like the environment selectors, an unresolved `--target` is a binding: the
helper exits non-zero and does not fall back to another plan.
The shell helper's `--target root` explicitly attests the project's root
`task_plan.md` and writes `.plan-attestation`, even while a named plan is active.
`--target <plan-id>` explicitly attests that named plan. Both forms override
`PLAN_ID` and honor `PWF_PLAN_ROOT`; without a project pin, paths are relative to
the current directory. Neither form changes `.planning/.active_plan`.
Empty, invalid or unresolved targets fail without falling back to another plan.
Named targets retain the shared resolver's containment and linked-directory
checks, and explicit targets reject linked plan files. `--target` is a write-only
form and cannot be combined with `--show` or `--clear`; extra arguments are
rejected. Every attestation write prints its plan and attestation paths first.
The atomic rename is the correctness guarantee. It prevents readers from seeing
a partially written attestation file. The `flock` call is only a cooperative
+37 -16
View File
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
@@ -6,7 +6,7 @@
# attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
#
# Resolution:
# 1. --target root → legacy ./task_plan.md, or --target <plan-id> → ./.planning/<plan-id>/
# 1. --target root or <plan-id> → root or named plan under $PWF_PLAN_ROOT (default: cwd)
# 2. $PLAN_ID env var → ./.planning/$PLAN_ID/
# 3. ./.planning/.active_plan
# 4. Newest ./.planning/<dir>/ by mtime
@@ -47,12 +47,32 @@ resolve_from_slug_cwd() {
resolve_plan_file() {
if [ -n "${target:-}" ]; then
if [ "${target}" = "root" ]; then
[ -f "./task_plan.md" ] || return 1
printf "%s\n" "./task_plan.md"
target_root="."
if [ -n "${PWF_PLAN_ROOT:-}" ]; then
# Match the shared resolver's supported absolute local pins.
# A valid project-root junction is intentional, so resolve it.
case "${PWF_PLAN_ROOT}" in
\\\\*|//*|[A-Za-z]:[!\\/]*) return 1 ;;
/*|[A-Za-z]:[\\/]*) ;;
*) return 1 ;;
esac
target_root="$(cd "${PWF_PLAN_ROOT}" 2>/dev/null && pwd -P)" || return 1
fi
[ ! -L "${target_root}/task_plan.md" ] || return 1
[ -f "${target_root}/task_plan.md" ] || return 1
printf "%s\n" "${target_root}/task_plan.md"
return 0
fi
slug_is_valid "${target}" || return 1
target_dir="./.planning/${target}"
# Reuse the binding, project-pin and containment rules. A flag replaces
# PLAN_ID for this call only; it does not change the shared pointer.
[ -f "${RESOLVER}" ] || return 1
target_dir="$(PLAN_ID="${target}" sh "${RESOLVER}" 2>/dev/null)" || return 1
[ -n "${target_dir}" ] || return 1
# Use the shell's physical path spelling: native Windows backslashes
# make sha256sum prefix its output with an escaped-filename marker.
target_dir="$(cd "${target_dir}" 2>/dev/null && pwd -P)" || return 1
[ ! -L "${target_dir}/task_plan.md" ] || return 1
[ -f "${target_dir}/task_plan.md" ] || return 1
printf "%s\n" "${target_dir}/task_plan.md"
return 0
@@ -96,7 +116,9 @@ resolve_plan_file() {
attestation_path_for() {
plan_file="$1"
plan_dir="$(dirname "${plan_file}")"
if [ "${plan_dir}" = "." ]; then
if [ "${target:-}" = "root" ]; then
printf "%s\n" "${plan_dir}/.plan-attestation"
elif [ "${plan_dir}" = "." ]; then
# Legacy mode: store at project root.
printf "%s\n" "./.plan-attestation"
else
@@ -118,21 +140,20 @@ compute_hash() {
mode="attest"
target=""
usage_error() {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
case "${1:-}" in
--show) mode="show" ;;
--clear) mode="clear" ;;
--show) [ "$#" -eq 1 ] || usage_error; mode="show" ;;
--clear) [ "$#" -eq 1 ] || usage_error; mode="clear" ;;
--target)
[ "$#" -eq 2 ] || {
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
}
[ "$#" -eq 2 ] || usage_error
[ -n "$2" ] || usage_error
target="$2"
;;
"") mode="attest" ;;
*)
printf "Usage: %s [--show|--clear|--target root|<plan-id>]\n" "$0" >&2
exit 2
;;
"") [ "$#" -eq 0 ] || usage_error ;;
*) usage_error ;;
esac
plan_file="$(resolve_plan_file)" || {
+144
View File
@@ -255,6 +255,150 @@ class PlanAttestationTests(unittest.TestCase):
)
self.assertFalse((self.tmp / ".plan-attestation").exists())
def test_empty_target_refuses_without_attesting_active_plan(self) -> None:
active = self.tmp / ".planning" / "active"
active.mkdir(parents=True)
(active / "task_plan.md").write_text("active", encoding="utf-8")
(self.tmp / "task_plan.md").write_text("root", encoding="utf-8")
result = self._run("--target", "")
self.assertNotEqual(0, result.returncode)
self.assertFalse((active / ".attestation").exists())
self.assertFalse((self.tmp / ".plan-attestation").exists())
def test_extra_arguments_refuse_without_clearing_another_plan(self) -> None:
active = self.tmp / ".planning" / "active"
active.mkdir(parents=True)
(active / "task_plan.md").write_text("active", encoding="utf-8")
(self.tmp / "task_plan.md").write_text("root", encoding="utf-8")
root_attest = self.tmp / ".plan-attestation"
active_attest = active / ".attestation"
for args in (("--show", "--target", "root"),
("--clear", "--target", "root"),
("--target", "root", "--clear"), ("", "extra")):
with self.subTest(args=args):
root_attest.write_text("root sentinel", encoding="utf-8")
active_attest.write_text("active sentinel", encoding="utf-8")
result = self._run(*args)
self.assertNotEqual(0, result.returncode)
self.assertEqual("root sentinel", root_attest.read_text())
self.assertEqual("active sentinel", active_attest.read_text())
def test_targets_honor_project_pin_and_preserve_active_pointer(self) -> None:
pinned = self.tmp / "pinned"
for project in (self.tmp, pinned):
active = project / ".planning" / "active"
active.mkdir(parents=True)
(active / "task_plan.md").write_text(str(project), encoding="utf-8")
(project / "task_plan.md").write_text("root " + str(project), encoding="utf-8")
(project / ".planning" / ".active_plan").write_bytes(b"active\r\n")
for target, plan, attestation in (
("root", pinned / "task_plan.md", pinned / ".plan-attestation"),
("active", pinned / ".planning/active/task_plan.md", pinned / ".planning/active/.attestation"),
):
with self.subTest(target=target):
result = self._run("--target", target, env={
"PWF_PLAN_ROOT": pinned.as_posix(), "PLAN_ID": "missing"})
self.assertEqual(0, result.returncode, result.stderr)
self.assertTrue(attestation.exists(), result.stdout)
self.assertEqual(sha256_of(plan), attestation.read_text().strip())
self.assertFalse((self.tmp / ".plan-attestation").exists())
self.assertFalse((self.tmp / ".planning/active/.attestation").exists())
self.assertEqual(b"active\r\n", (pinned / ".planning/.active_plan").read_bytes())
self.assertEqual(b"active\r\n", (self.tmp / ".planning/.active_plan").read_bytes())
def test_targets_reject_invalid_project_pin_without_cwd_fallback(self) -> None:
active = self.tmp / ".planning" / "active"
active.mkdir(parents=True)
(active / "task_plan.md").write_text("active", encoding="utf-8")
(self.tmp / "task_plan.md").write_text("root", encoding="utf-8")
for pin in ((self.tmp / "missing").as_posix(), "."):
for target in ("root", "active"):
with self.subTest(pin=pin, target=target):
result = self._run("--target", target, env={"PWF_PLAN_ROOT": pin})
self.assertNotEqual(0, result.returncode)
self.assertFalse((self.tmp / ".plan-attestation").exists())
self.assertFalse((active / ".attestation").exists())
@unittest.skipUnless(os.name == "nt", "Windows native path spelling")
def test_named_target_hashes_native_windows_project_pin(self) -> None:
pinned = self.tmp / "pinned"
active = pinned / ".planning" / "active"
active.mkdir(parents=True)
plan = active / "task_plan.md"
plan.write_text("pinned plan", encoding="utf-8")
result = self._run("--target", "active", env={"PWF_PLAN_ROOT": str(pinned)})
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(sha256_of(plan), (active / ".attestation").read_text().strip())
def _make_directory_link(self, link: Path, target: Path) -> None:
if os.name == "nt":
made = subprocess.run(
["cmd", "/d", "/c", "mklink", "/J", str(link), str(target)],
capture_output=True, text=True, check=False,
)
if made.returncode:
self.skipTest("junction creation unavailable: " + made.stderr.strip())
else:
try:
link.symlink_to(target, target_is_directory=True)
except OSError as exc:
self.skipTest(f"symlink creation unavailable: {exc}")
def test_target_rejects_linked_plan_directory(self) -> None:
project = self.tmp / "project"
(project / ".planning").mkdir(parents=True)
outside = self.tmp / "outside"
outside.mkdir()
(outside / "task_plan.md").write_text("outside", encoding="utf-8")
self._make_directory_link(project / ".planning/linked", outside)
result = self._run("--target", "linked", cwd=project)
self.assertNotEqual(0, result.returncode)
self.assertFalse((outside / ".attestation").exists())
def test_target_rejects_planning_directory_escape(self) -> None:
project = self.tmp / "project"
project.mkdir()
outside = self.tmp / "outside"
active = outside / "active"
active.mkdir(parents=True)
(active / "task_plan.md").write_text("outside", encoding="utf-8")
self._make_directory_link(project / ".planning", outside)
result = self._run("--target", "active", cwd=project)
self.assertNotEqual(0, result.returncode)
self.assertFalse((active / ".attestation").exists())
def test_root_target_accepts_valid_linked_project_pin(self) -> None:
project = self.tmp / "project"
project.mkdir()
plan = project / "task_plan.md"
plan.write_text("pinned root", encoding="utf-8")
(self.tmp / "task_plan.md").write_text("cwd decoy", encoding="utf-8")
link = self.tmp / "project-link"
self._make_directory_link(link, project)
result = self._run("--target", "root", env={"PWF_PLAN_ROOT": link.as_posix()})
self.assertEqual(0, result.returncode, result.stderr)
attestation = project / ".plan-attestation"
self.assertTrue(attestation.exists(), result.stdout)
self.assertEqual(sha256_of(plan), attestation.read_text().strip())
self.assertFalse((self.tmp / ".plan-attestation").exists())
def test_targets_reject_linked_plan_files(self) -> None:
outside = self.tmp / "outside.md"
outside.write_text("outside", encoding="utf-8")
active = self.tmp / ".planning" / "active"
active.mkdir(parents=True)
for target, plan in (("root", self.tmp / "task_plan.md"),
("active", active / "task_plan.md")):
try:
plan.symlink_to(outside)
except OSError as exc:
self.skipTest(f"file symlink creation unavailable: {exc}")
with self.subTest(target=target):
result = self._run("--target", target)
self.assertNotEqual(0, result.returncode)
self.assertFalse((self.tmp / ".plan-attestation").exists())
self.assertFalse((active / ".attestation").exists())
def test_no_plan_exits_nonzero(self) -> None:
result = self._run()
self.assertNotEqual(0, result.returncode)