From: Thomas Walker Lynch Date: Sat, 20 Sep 2025 16:34:53 +0000 (+0000) Subject: passes small test X-Git-Url: https://git.reasoningtechnology.com/usr/lib/python2.7/quopri.py?a=commitdiff_plain;h=b422beb7e60ee6dd7af0e8eaaf7180f1de9658b9;p=Man-In-Grey passes small test --- diff --git a/developer/source/Man_In_Grey b/developer/source/Man_In_Grey index d4180e3..2ad1606 100644 --- a/developer/source/Man_In_Grey +++ b/developer/source/Man_In_Grey @@ -1,57 +1,62 @@ #!/usr/bin/env bash -# Man_In_Grey — canonical entrypoint for Man_In_Grey -# - Resolves repo root via this script’s location (…/release/shell/) -# - Picks gasket at release//man_in_grey_apply when present -# - Falls back to Python inner executor -# - Always invokes the Python orchestrator Man_In_Grey.py - +# man_in_grey — canonical entrypoint for Man_In_Grey +# - Resolves repo → release dirs +# - Requires the privileged gasket (single code path) +# - Always invokes the Python orchestrator with --apply-cmd set -euo pipefail -# --- resolve paths --- -_this="${BASH_SOURCE[0]}" +# --- resolve this script’s absolute path (portable) --- +_this="${BASH_SOURCE[0]:-$0}" + if command -v realpath >/dev/null 2>&1; then - _this_abs="$(realpath "$_this")" + _this_abs="$(realpath -- "$_this")" else - _this_abs="$(readlink -f "$_this" 2>/dev/null || (cd "$(dirname "$_this")" && pwd -P)/"$(basename "$_this"))" + if _tmp="$(readlink -f -- "$_this" 2>/dev/null)"; then + _this_abs="${_tmp}" + else + _dir="$(cd "$(dirname -- "$_this")" && pwd -P)" + _base="$(basename -- "$_this")" + _this_abs="${_dir}/${_base}" + fi fi -_shell_dir="$(cd "$(dirname "$_this_abs")" && pwd -P)" # .../release/shell -_release_dir="$(cd "$_shell_dir/.." && pwd -P)" # .../release -_repo_root="$(cd "$_release_dir/.." && pwd -P)" # repo root - -_py_release="$_release_dir/python3" -_py_dev="$_repo_root/developer/source" - -_py_entry="" -if [[ -f "$_py_release/Man_In_Grey.py" ]]; then - _py_entry="$_py_release/Man_In_Grey.py" -elif [[ -f "$_py_dev/Man_In_Grey.py" ]]; then - _py_entry="$_py_dev/Man_In_Grey.py" +_shell_dir="$(cd "$(dirname -- "$_this_abs")" && pwd -P)" # .../release/shell +_release_dir="$(cd "${_shell_dir}/.." && pwd -P)" # .../release +_repo_root="$(cd "${_release_dir}/.." && pwd -P)" # repo root + +# --- orchestrator path (prefer released copy) --- +_py_release="${_release_dir}/python3/Man_In_Grey.py" +_py_dev="${_repo_root}/developer/source/Man_In_Grey.py" +if [[ -f "$_py_release" ]]; then + _py_entry="$_py_release" +elif [[ -f "$_py_dev" ]]; then + _py_entry="$_py_dev" else echo "error: Man_In_Grey.py not found in release/python3/ or developer/source/" >&2 exit 2 fi -# --- arch normalize --- +# --- arch normalize (uname -m → release/) --- _arch_raw="$(uname -m | tr '[:upper:]' '[:lower:]')" case "$_arch_raw" in - amd64|x64) _arch="x86_64" ;; - x86_64) _arch="x86_64" ;; - i386|i486|i586|i686) _arch="i686" ;; - arm64|aarch64) _arch="aarch64" ;; - armv7l) _arch="armv7l" ;; - armv6l) _arch="armv6l" ;; - riscv64) _arch="riscv64" ;; - ppc64le|powerpc64le) _arch="ppc64le" ;; - s390x) _arch="s390x" ;; - *) _arch="$_arch_raw" ;; + amd64|x64) _arch="x86_64" ;; + x86_64) _arch="x86_64" ;; + i386|i486|i586|i686) _arch="i686" ;; + arm64|aarch64) _arch="aarch64";; + armv7l) _arch="armv7l" ;; + armv6l) _arch="armv6l" ;; + riscv64) _arch="riscv64";; + ppc64le|powerpc64le) _arch="ppc64le";; + s390x) _arch="s390x" ;; + *) _arch="$_arch_raw" ;; esac -_gasket="$_release_dir/$_arch/man_in_grey_apply" -_apply_args=() -if [[ -x "$_gasket" ]]; then - _apply_args=(--apply-cmd "$_gasket") +_gasket="${_release_dir}/${_arch}/man_in_grey_apply" +if [[ ! -x "$_gasket" ]]; then + echo "error: gasket missing: ${_gasket}" >&2 + echo "hint: from \$REPO_HOME/developer: run compile then release" >&2 + exit 2 fi -# --- run orchestrator --- -exec python3 "$_py_entry" "${_apply_args[@]}" "$@" +# --- run orchestrator (single code path via gasket) --- +exec "${PYTHON:-python3}" "$_py_entry" --apply-cmd "$_gasket" "$@" diff --git a/developer/source/Man_In_Grey.py b/developer/source/Man_In_Grey.py index e10314b..22853b6 100644 --- a/developer/source/Man_In_Grey.py +++ b/developer/source/Man_In_Grey.py @@ -201,19 +201,16 @@ def _find_inner_py(repo_root: Path)-> Path|None: cand = repo_root/"release"/"python3"/"executor_inner.py" return cand if cand.is_file() else None -def _apply_via_gasket(cbor_bytes: bytes ,apply_cmd: Path ,args)-> int: - cmd = [ - str(apply_cmd) - ,"--plan" ,"-" - ] - if args.phase_2_print: cmd.append("--phase-2-print") - if args.phase_2_then_stop: cmd.append("--phase-2-then-stop") - if args.phase_2_wellformed_then_stop: cmd.append("--phase-2-wellformed-then-stop") - if args.phase_2_sanity1_then_stop: cmd.append("--phase-2-sanity1-then-stop") - if args.phase_2_validity_then_stop: cmd.append("--phase-2-validity-then-stop") - if args.phase_2_sanity2_then_stop: cmd.append("--phase-2-sanity2-then-stop") - proc = subprocess.run(cmd ,input=cbor_bytes) - return proc.returncode +def _apply_via_gasket(cbor_bytes: bytes, apply_cmd: Path, args) -> int: + cmd = [str(apply_cmd), "--plan", "-"] # <— tell gasket to read from stdin + if args.phase_2_print: cmd.append("--phase-2-print") + if args.phase_2_then_stop: cmd.append("--phase-2-then-stop") + if args.phase_2_wellformed_then_stop: cmd.append("--phase-2-wellformed-then-stop") + if args.phase_2_sanity1_then_stop: cmd.append("--phase-2-sanity1-then-stop") + if args.phase_2_validity_then_stop: cmd.append("--phase-2-validity-then-stop") + if args.phase_2_sanity2_then_stop: cmd.append("--phase-2-sanity2-then-stop") + proc = subprocess.run(cmd, input=cbor_bytes) + return proc.returncode def _apply_via_inner_py(cbor_bytes: bytes ,inner_py: Path ,args)-> int: cmd = [ diff --git a/developer/source/executor_inner.py b/developer/source/executor_inner.py index 433b705..eb6bf5c 100644 --- a/developer/source/executor_inner.py +++ b/developer/source/executor_inner.py @@ -158,6 +158,9 @@ def _safe_open_dir(dpath: str)-> int: os.close(fd) ; raise OSError("not a directory") return fd + + + def check_sanity_2(journal: Journal)-> list[str]: errs: list[str] = [] opened: dict[str ,int] = {} @@ -174,14 +177,18 @@ def check_sanity_2(journal: Journal)-> list[str]: except Exception as e: errs.append(f"[{i}] cannot open destination dir: {d} ({e})") - # also warn on multiple writes to same (d,f) without displacement/delete - seen: set[tuple[str ,str]] = set() - for i ,cmd in enumerate(journal.command_list ,start=1): + # detect multiple writes to same target without a reset (displace/delete) + last_action: dict[tuple[str, str], str] = {} + for i, cmd in enumerate(journal.command_list, start=1): ad = cmd.arg_dict - key = (ad.get("write_file_dpath_str") ,ad.get("write_file_fname")) - if key in seen and cmd.name_str == "copy": - errs.append(f"[{i}] multiple writes to same target without prior displace/delete: {_dst_from(ad)}") - seen.add(key) + key = (ad.get("write_file_dpath_str"), ad.get("write_file_fname")) + op = cmd.name_str + if op == "copy": + if last_action.get(key) == "copy": + errs.append(f"[{i}] multiple writes to same target without prior displace/delete: {_dst_from(ad)}") + last_action[key] = "copy" + elif op in {"displace", "delete"}: + last_action[key] = op finally: for fd in opened.values(): @@ -378,56 +385,86 @@ def run_executor_inner( # --- main stays a thin arg wrapper ------------------------------------------ -def main(argv: list[str]|None=None)-> int: +# --- plan input helpers ------------------------------------------------------- + +def _read_fd_all(fd: int) -> bytes: + "Read all bytes from an already-open file descriptor without closing it." + chunks: list[bytes] = [] + while True: + try: + b = os.read(fd, 65536) + except InterruptedError: + continue + if not b: + break + chunks.append(b) + return b"".join(chunks) + +def _read_plan_bytes_from_args(args) -> bytes: + """ + Input priority: + 1) --plan-fd (gasket path; do not close fd) + 2) --plan - (stdin) + 3) --plan (read from filesystem) + """ + if getattr(args, "plan_fd", -1) is not None and args.plan_fd >= 0: + return _read_fd_all(args.plan_fd) + if args.plan in ("", "-"): + return sys.stdin.buffer.read() + return Path(args.plan).read_bytes() +def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser( - prog="executor_inner.py" - ,description="Man_In_Gray inner executor (decode → validate → apply)" + prog="executor_inner.py", + description="Man_In_Grey inner executor (decode → validate → apply)" ) - ap.add_argument("--plan" ,required=True ,help="path to CBOR plan file") - ap.add_argument("--phase-2-print" ,action="store_true" ,help="print decoded journal") - ap.add_argument("--phase-2-then-stop" ,action="store_true" ,help="stop after print (no apply)") - ap.add_argument("--phase-2-wellformed-then-stop" ,action="store_true" ,help="stop after wellformed checks") - ap.add_argument("--phase-2-sanity1-then-stop" ,action="store_true" ,help="stop after sanity-1 checks") - ap.add_argument("--phase-2-validity-then-stop" ,action="store_true" ,help="stop after validity checks") - ap.add_argument("--phase-2-sanity2-then-stop" ,action="store_true" ,help="stop after sanity-2 checks") - ap.add_argument("--plan" ,default="" ,help="path to CBOR plan file or '-' for stdin") - ap.add_argument("--plan-fd" ,type=int ,default=-1 ,help=argparse.SUPPRESS) + # Single --plan plus a hidden --plan-fd used by the gasket + ap.add_argument( + "--plan", + default="-", + help="path to CBOR plan file or '-' for stdin" + ) + ap.add_argument( + "--plan-fd", + type=int, + default=-1, + help=argparse.SUPPRESS + ) + + # phase-2 gates (same semantics as before) + ap.add_argument("--phase-2-print", action="store_true", help="print decoded journal") + ap.add_argument("--phase-2-then-stop", action="store_true", help="stop after print (no apply)") + ap.add_argument("--phase-2-wellformed-then-stop", action="store_true", help="stop after wellformed checks") + ap.add_argument("--phase-2-sanity1-then-stop", action="store_true", help="stop after sanity-1 checks") + ap.add_argument("--phase-2-validity-then-stop", action="store_true", help="stop after validity checks") + ap.add_argument("--phase-2-sanity2-then-stop", action="store_true", help="stop after sanity-2 checks") args = ap.parse_args(argv) - # load plan + # Read plan bytes from fd/stdin/file try: - if args.plan_fd >= 0: - import os as _os - data = _os.read(args.plan_fd ,1<<30) - elif args.plan == "-": - import sys as _sys - data = _sys.stdin.buffer.read() - elif args.plan: - data = Path(args.plan).read_bytes() - else: - print("error: either --plan or --plan-fd is required" ,file=sys.stderr) - return 2 + data = _read_plan_bytes_from_args(args) except Exception as e: - print(f"error: failed to read plan: {e}" ,file=sys.stderr) + print(f"error: failed to read plan: {e}", file=sys.stderr) return 2 + # Decode CBOR → Journal try: journal = _journal_from_cbor_bytes(data) except Exception as e: - print(f"error: failed to decode CBOR: {e}" ,file=sys.stderr) + print(f"error: failed to decode CBOR: {e}", file=sys.stderr) return 2 + # Run the pipeline return executor_inner( - journal - ,phase_2_print=args.phase_2_print - ,phase_2_then_stop=args.phase_2_then_stop - ,phase_2_wellformed_then_stop=args.phase_2_wellformed_then_stop - ,phase_2_sanity1_then_stop=args.phase_2_sanity1_then_stop - ,phase_2_validity_then_stop=args.phase_2_validity_then_stop - ,phase_2_sanity2_then_stop=args.phase_2_sanity2_then_stop + journal, + phase_2_print=args.phase_2_print, + phase_2_then_stop=args.phase_2_then_stop, + phase_2_wellformed_then_stop=args.phase_2_wellformed_then_stop, + phase_2_sanity1_then_stop=args.phase_2_sanity1_then_stop, + phase_2_validity_then_stop=args.phase_2_validity_then_stop, + phase_2_sanity2_then_stop=args.phase_2_sanity2_then_stop, ) if __name__ == "__main__": diff --git a/document/todo.txt b/document/todo.txt new file mode 100644 index 0000000..be8c0af --- /dev/null +++ b/document/todo.txt @@ -0,0 +1,6 @@ + +2025-09-20 16:21:44Z [Man_In_Grey:tester] + +port executor_outer.py to C integrate it with apply.c + +add more tests. diff --git a/release/python3/Man_In_Grey.py b/release/python3/Man_In_Grey.py index e10314b..22853b6 100755 --- a/release/python3/Man_In_Grey.py +++ b/release/python3/Man_In_Grey.py @@ -201,19 +201,16 @@ def _find_inner_py(repo_root: Path)-> Path|None: cand = repo_root/"release"/"python3"/"executor_inner.py" return cand if cand.is_file() else None -def _apply_via_gasket(cbor_bytes: bytes ,apply_cmd: Path ,args)-> int: - cmd = [ - str(apply_cmd) - ,"--plan" ,"-" - ] - if args.phase_2_print: cmd.append("--phase-2-print") - if args.phase_2_then_stop: cmd.append("--phase-2-then-stop") - if args.phase_2_wellformed_then_stop: cmd.append("--phase-2-wellformed-then-stop") - if args.phase_2_sanity1_then_stop: cmd.append("--phase-2-sanity1-then-stop") - if args.phase_2_validity_then_stop: cmd.append("--phase-2-validity-then-stop") - if args.phase_2_sanity2_then_stop: cmd.append("--phase-2-sanity2-then-stop") - proc = subprocess.run(cmd ,input=cbor_bytes) - return proc.returncode +def _apply_via_gasket(cbor_bytes: bytes, apply_cmd: Path, args) -> int: + cmd = [str(apply_cmd), "--plan", "-"] # <— tell gasket to read from stdin + if args.phase_2_print: cmd.append("--phase-2-print") + if args.phase_2_then_stop: cmd.append("--phase-2-then-stop") + if args.phase_2_wellformed_then_stop: cmd.append("--phase-2-wellformed-then-stop") + if args.phase_2_sanity1_then_stop: cmd.append("--phase-2-sanity1-then-stop") + if args.phase_2_validity_then_stop: cmd.append("--phase-2-validity-then-stop") + if args.phase_2_sanity2_then_stop: cmd.append("--phase-2-sanity2-then-stop") + proc = subprocess.run(cmd, input=cbor_bytes) + return proc.returncode def _apply_via_inner_py(cbor_bytes: bytes ,inner_py: Path ,args)-> int: cmd = [ diff --git a/release/python3/executor_inner.py b/release/python3/executor_inner.py index 433b705..eb6bf5c 100755 --- a/release/python3/executor_inner.py +++ b/release/python3/executor_inner.py @@ -158,6 +158,9 @@ def _safe_open_dir(dpath: str)-> int: os.close(fd) ; raise OSError("not a directory") return fd + + + def check_sanity_2(journal: Journal)-> list[str]: errs: list[str] = [] opened: dict[str ,int] = {} @@ -174,14 +177,18 @@ def check_sanity_2(journal: Journal)-> list[str]: except Exception as e: errs.append(f"[{i}] cannot open destination dir: {d} ({e})") - # also warn on multiple writes to same (d,f) without displacement/delete - seen: set[tuple[str ,str]] = set() - for i ,cmd in enumerate(journal.command_list ,start=1): + # detect multiple writes to same target without a reset (displace/delete) + last_action: dict[tuple[str, str], str] = {} + for i, cmd in enumerate(journal.command_list, start=1): ad = cmd.arg_dict - key = (ad.get("write_file_dpath_str") ,ad.get("write_file_fname")) - if key in seen and cmd.name_str == "copy": - errs.append(f"[{i}] multiple writes to same target without prior displace/delete: {_dst_from(ad)}") - seen.add(key) + key = (ad.get("write_file_dpath_str"), ad.get("write_file_fname")) + op = cmd.name_str + if op == "copy": + if last_action.get(key) == "copy": + errs.append(f"[{i}] multiple writes to same target without prior displace/delete: {_dst_from(ad)}") + last_action[key] = "copy" + elif op in {"displace", "delete"}: + last_action[key] = op finally: for fd in opened.values(): @@ -378,56 +385,86 @@ def run_executor_inner( # --- main stays a thin arg wrapper ------------------------------------------ -def main(argv: list[str]|None=None)-> int: +# --- plan input helpers ------------------------------------------------------- + +def _read_fd_all(fd: int) -> bytes: + "Read all bytes from an already-open file descriptor without closing it." + chunks: list[bytes] = [] + while True: + try: + b = os.read(fd, 65536) + except InterruptedError: + continue + if not b: + break + chunks.append(b) + return b"".join(chunks) + +def _read_plan_bytes_from_args(args) -> bytes: + """ + Input priority: + 1) --plan-fd (gasket path; do not close fd) + 2) --plan - (stdin) + 3) --plan (read from filesystem) + """ + if getattr(args, "plan_fd", -1) is not None and args.plan_fd >= 0: + return _read_fd_all(args.plan_fd) + if args.plan in ("", "-"): + return sys.stdin.buffer.read() + return Path(args.plan).read_bytes() +def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser( - prog="executor_inner.py" - ,description="Man_In_Gray inner executor (decode → validate → apply)" + prog="executor_inner.py", + description="Man_In_Grey inner executor (decode → validate → apply)" ) - ap.add_argument("--plan" ,required=True ,help="path to CBOR plan file") - ap.add_argument("--phase-2-print" ,action="store_true" ,help="print decoded journal") - ap.add_argument("--phase-2-then-stop" ,action="store_true" ,help="stop after print (no apply)") - ap.add_argument("--phase-2-wellformed-then-stop" ,action="store_true" ,help="stop after wellformed checks") - ap.add_argument("--phase-2-sanity1-then-stop" ,action="store_true" ,help="stop after sanity-1 checks") - ap.add_argument("--phase-2-validity-then-stop" ,action="store_true" ,help="stop after validity checks") - ap.add_argument("--phase-2-sanity2-then-stop" ,action="store_true" ,help="stop after sanity-2 checks") - ap.add_argument("--plan" ,default="" ,help="path to CBOR plan file or '-' for stdin") - ap.add_argument("--plan-fd" ,type=int ,default=-1 ,help=argparse.SUPPRESS) + # Single --plan plus a hidden --plan-fd used by the gasket + ap.add_argument( + "--plan", + default="-", + help="path to CBOR plan file or '-' for stdin" + ) + ap.add_argument( + "--plan-fd", + type=int, + default=-1, + help=argparse.SUPPRESS + ) + + # phase-2 gates (same semantics as before) + ap.add_argument("--phase-2-print", action="store_true", help="print decoded journal") + ap.add_argument("--phase-2-then-stop", action="store_true", help="stop after print (no apply)") + ap.add_argument("--phase-2-wellformed-then-stop", action="store_true", help="stop after wellformed checks") + ap.add_argument("--phase-2-sanity1-then-stop", action="store_true", help="stop after sanity-1 checks") + ap.add_argument("--phase-2-validity-then-stop", action="store_true", help="stop after validity checks") + ap.add_argument("--phase-2-sanity2-then-stop", action="store_true", help="stop after sanity-2 checks") args = ap.parse_args(argv) - # load plan + # Read plan bytes from fd/stdin/file try: - if args.plan_fd >= 0: - import os as _os - data = _os.read(args.plan_fd ,1<<30) - elif args.plan == "-": - import sys as _sys - data = _sys.stdin.buffer.read() - elif args.plan: - data = Path(args.plan).read_bytes() - else: - print("error: either --plan or --plan-fd is required" ,file=sys.stderr) - return 2 + data = _read_plan_bytes_from_args(args) except Exception as e: - print(f"error: failed to read plan: {e}" ,file=sys.stderr) + print(f"error: failed to read plan: {e}", file=sys.stderr) return 2 + # Decode CBOR → Journal try: journal = _journal_from_cbor_bytes(data) except Exception as e: - print(f"error: failed to decode CBOR: {e}" ,file=sys.stderr) + print(f"error: failed to decode CBOR: {e}", file=sys.stderr) return 2 + # Run the pipeline return executor_inner( - journal - ,phase_2_print=args.phase_2_print - ,phase_2_then_stop=args.phase_2_then_stop - ,phase_2_wellformed_then_stop=args.phase_2_wellformed_then_stop - ,phase_2_sanity1_then_stop=args.phase_2_sanity1_then_stop - ,phase_2_validity_then_stop=args.phase_2_validity_then_stop - ,phase_2_sanity2_then_stop=args.phase_2_sanity2_then_stop + journal, + phase_2_print=args.phase_2_print, + phase_2_then_stop=args.phase_2_then_stop, + phase_2_wellformed_then_stop=args.phase_2_wellformed_then_stop, + phase_2_sanity1_then_stop=args.phase_2_sanity1_then_stop, + phase_2_validity_then_stop=args.phase_2_validity_then_stop, + phase_2_sanity2_then_stop=args.phase_2_sanity2_then_stop, ) if __name__ == "__main__": diff --git a/release/shell/Man_In_Grey b/release/shell/Man_In_Grey index d4180e3..2ad1606 100755 --- a/release/shell/Man_In_Grey +++ b/release/shell/Man_In_Grey @@ -1,57 +1,62 @@ #!/usr/bin/env bash -# Man_In_Grey — canonical entrypoint for Man_In_Grey -# - Resolves repo root via this script’s location (…/release/shell/) -# - Picks gasket at release//man_in_grey_apply when present -# - Falls back to Python inner executor -# - Always invokes the Python orchestrator Man_In_Grey.py - +# man_in_grey — canonical entrypoint for Man_In_Grey +# - Resolves repo → release dirs +# - Requires the privileged gasket (single code path) +# - Always invokes the Python orchestrator with --apply-cmd set -euo pipefail -# --- resolve paths --- -_this="${BASH_SOURCE[0]}" +# --- resolve this script’s absolute path (portable) --- +_this="${BASH_SOURCE[0]:-$0}" + if command -v realpath >/dev/null 2>&1; then - _this_abs="$(realpath "$_this")" + _this_abs="$(realpath -- "$_this")" else - _this_abs="$(readlink -f "$_this" 2>/dev/null || (cd "$(dirname "$_this")" && pwd -P)/"$(basename "$_this"))" + if _tmp="$(readlink -f -- "$_this" 2>/dev/null)"; then + _this_abs="${_tmp}" + else + _dir="$(cd "$(dirname -- "$_this")" && pwd -P)" + _base="$(basename -- "$_this")" + _this_abs="${_dir}/${_base}" + fi fi -_shell_dir="$(cd "$(dirname "$_this_abs")" && pwd -P)" # .../release/shell -_release_dir="$(cd "$_shell_dir/.." && pwd -P)" # .../release -_repo_root="$(cd "$_release_dir/.." && pwd -P)" # repo root - -_py_release="$_release_dir/python3" -_py_dev="$_repo_root/developer/source" - -_py_entry="" -if [[ -f "$_py_release/Man_In_Grey.py" ]]; then - _py_entry="$_py_release/Man_In_Grey.py" -elif [[ -f "$_py_dev/Man_In_Grey.py" ]]; then - _py_entry="$_py_dev/Man_In_Grey.py" +_shell_dir="$(cd "$(dirname -- "$_this_abs")" && pwd -P)" # .../release/shell +_release_dir="$(cd "${_shell_dir}/.." && pwd -P)" # .../release +_repo_root="$(cd "${_release_dir}/.." && pwd -P)" # repo root + +# --- orchestrator path (prefer released copy) --- +_py_release="${_release_dir}/python3/Man_In_Grey.py" +_py_dev="${_repo_root}/developer/source/Man_In_Grey.py" +if [[ -f "$_py_release" ]]; then + _py_entry="$_py_release" +elif [[ -f "$_py_dev" ]]; then + _py_entry="$_py_dev" else echo "error: Man_In_Grey.py not found in release/python3/ or developer/source/" >&2 exit 2 fi -# --- arch normalize --- +# --- arch normalize (uname -m → release/) --- _arch_raw="$(uname -m | tr '[:upper:]' '[:lower:]')" case "$_arch_raw" in - amd64|x64) _arch="x86_64" ;; - x86_64) _arch="x86_64" ;; - i386|i486|i586|i686) _arch="i686" ;; - arm64|aarch64) _arch="aarch64" ;; - armv7l) _arch="armv7l" ;; - armv6l) _arch="armv6l" ;; - riscv64) _arch="riscv64" ;; - ppc64le|powerpc64le) _arch="ppc64le" ;; - s390x) _arch="s390x" ;; - *) _arch="$_arch_raw" ;; + amd64|x64) _arch="x86_64" ;; + x86_64) _arch="x86_64" ;; + i386|i486|i586|i686) _arch="i686" ;; + arm64|aarch64) _arch="aarch64";; + armv7l) _arch="armv7l" ;; + armv6l) _arch="armv6l" ;; + riscv64) _arch="riscv64";; + ppc64le|powerpc64le) _arch="ppc64le";; + s390x) _arch="s390x" ;; + *) _arch="$_arch_raw" ;; esac -_gasket="$_release_dir/$_arch/man_in_grey_apply" -_apply_args=() -if [[ -x "$_gasket" ]]; then - _apply_args=(--apply-cmd "$_gasket") +_gasket="${_release_dir}/${_arch}/man_in_grey_apply" +if [[ ! -x "$_gasket" ]]; then + echo "error: gasket missing: ${_gasket}" >&2 + echo "hint: from \$REPO_HOME/developer: run compile then release" >&2 + exit 2 fi -# --- run orchestrator --- -exec python3 "$_py_entry" "${_apply_args[@]}" "$@" +# --- run orchestrator (single code path via gasket) --- +exec "${PYTHON:-python3}" "$_py_entry" --apply-cmd "$_gasket" "$@" diff --git a/tester/test_0/Man_In_Grey_input_acceptance.py b/tester/test_0/Man_In_Grey_input_acceptance.py new file mode 100644 index 0000000..2608280 --- /dev/null +++ b/tester/test_0/Man_In_Grey_input_acceptance.py @@ -0,0 +1,26 @@ +# Man_In_Grey acceptance filter (default template) +# Return True to include a config file ,False to skip it. +# You receive a PlanProvenance object named `prov`. +# +# Common fields: +# prov.stage_root_dpath : Path +# prov.config_abs_fpath : Path +# prov.config_rel_fpath : Path +# prov.read_dir_dpath : Path +# prov.read_fname : str +# +# 1) Accept everything (default): +# def accept(prov): +# return True +# +# 2) Only a namespace: +# def accept(prov): +# return prov.config_rel_fpath.as_posix().startswith("dns/") +# +# 3) Exclude editor junk: +# def accept(prov): +# r = prov.config_rel_fpath.as_posix() +# return not (r.endswith("~") or r.endswith(".swp")) +# +def accept(prov): + return True diff --git a/tester/test_0/cat_stage b/tester/test_0/cat_stage new file mode 100755 index 0000000..4aa0ef6 --- /dev/null +++ b/tester/test_0/cat_stage @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + +set -euo pipefail +set -x + +find "$1" -type f -exec cat_named {} \; + diff --git a/tester/test_0/clean_out b/tester/test_0/clean_out new file mode 100755 index 0000000..5d6e333 --- /dev/null +++ b/tester/test_0/clean_out @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + +set -euo pipefail +set -x + +find stage_test_0_out -type f -exec rm \-f {} \; + diff --git a/tester/test_0/ls_stage b/tester/test_0/ls_stage new file mode 100755 index 0000000..ea3750f --- /dev/null +++ b/tester/test_0/ls_stage @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + +set -euo pipefail +set -x + +find "$1" -type f -print + diff --git a/tester/test_0/run b/tester/test_0/run index 6179ab1..4e8e3da 100755 --- a/tester/test_0/run +++ b/tester/test_0/run @@ -1,92 +1,7 @@ #!/usr/bin/env bash -set -euo pipefail - -# discover repo root from here script_afp=$(realpath "${BASH_SOURCE[0]}") -TESTER_DIR="$(cd "$(dirname "$script_afp")/.." && pwd -P)" -REPO_HOME="$(cd "$TESTER_DIR/.." && pwd -P)" - -ENTRY="$REL_SHELL/Man_In_Grey" -GASKET="$REL_ARCH/man_in_grey_apply" - -# tester has a path to `release/shell` -ENTRY="Man_In_Grey" - -# sanity -[[ -x "$ENTRY" ]] || { echo "❌ missing entrypoint: $ENTRY (did you run developer/release?)" >&2; exit 2; } - -# ensure tester won’t hit privileged gasket refusal: -ARCH_RAW=$(uname -m | tr '[:upper:]' '[:lower:]') -case "$ARCH_RAW" in - amd64|x64) ARCH="x86_64" ;; - x86_64) ARCH="x86_64" ;; - i386|i486|i586|i686) ARCH="i686" ;; - arm64|aarch64) ARCH="aarch64" ;; - armv7l) ARCH="armv7l" ;; - armv6l) ARCH="armv6l" ;; - riscv64) ARCH="riscv64" ;; - ppc64le|powerpc64le) ARCH="ppc64le" ;; - s390x) ARCH="s390x" ;; - *) ARCH="$ARCH_RAW" ;; -esac -GASKET="$REPO_HOME/release/$ARCH/man_in_grey_apply" - -if [[ -x "$GASKET" && -u "$GASKET" ]]; then - echo "⚠️ Gasket is blessed (setuid-root) but tester is sudo-less." - echo " Run: sudo ./tool/unbless" - exit 1 -fi - -# fresh output dir -rm -rf -- "$OUT" -mkdir -p "$OUT" -echo "▶️ Running Man_In_Grey on tester/$STAGE → $OUT" -# Run planner → CBOR → apply (unprivileged). Default filter will be emitted in CWD if missing. -( cd "$TESTER_DIR" && \ - "$ENTRY" \ - --stage "$STAGE" \ - --phase-1-print \ - --phase-2-print ) - -echo "✅ Apply finished. Verifying…" - -fail=0 - -# expected artifacts (from your sample stage) -chk() { - local path="$1" desc="$2" - if [[ -f "$path" ]]; then - echo " ✓ $desc ($path)" - else - echo " ✗ $desc missing ($path)"; fail=1 - fi -} - -# files to expect -chk "$OUT/unbound_conf" "DNS base file" -chk "$OUT/net/unbound.conf" "DNS net override" -chk "$OUT/web/nginx.conf" "web nginx.conf" - -# content spot checks (adjust to your test content) -if [[ -f "$OUT/net/unbound.conf" ]]; then - grep -q 'verbosity: 1' "$OUT/net/unbound.conf" \ - && echo " ✓ verbosity content OK" \ - || { echo " ✗ expected 'verbosity: 1' in net/unbound.conf"; fail=1; } -fi - -if [[ -f "$OUT/web/nginx.conf" ]]; then - grep -q 'listen 8080' "$OUT/web/nginx.conf" \ - && echo " ✓ nginx listen OK" \ - || { echo " ✗ expected 'listen 8080' in web/nginx.conf"; fail=1; } -fi - -# mode spot check (0444 example; chmod prints in octal differently across distros, use stat) -if [[ -f "$OUT/unbound_conf" ]]; then - mode=$(stat -c '%a' "$OUT/unbound_conf" 2>/dev/null || stat -f '%Lp' "$OUT/unbound_conf") - [[ "$mode" == "444" ]] \ - && echo " ✓ mode unbound_conf is 0444" \ - || { echo " ⚠︎ mode unbound_conf is $mode (expected 444)"; :; } -fi +set -euo pipefail +set -x -[[ $fail -eq 0 ]] && echo "🎉 test_0 PASS" || { echo "❌ test_0 FAIL"; exit 1; } +$RELEASE_SHELL/Man_In_Grey --stage stage_test_0 diff --git a/tester/test_0/run_check b/tester/test_0/run_check new file mode 100755 index 0000000..4b02ea6 --- /dev/null +++ b/tester/test_0/run_check @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + +set -euo pipefail +set -x + +./clean_out + +echo "first run" +./run +diff -qr stage_test_0_out stage_test_0_out_expected_0 + +echo "second run" +./run +diff -qr stage_test_0_out stage_test_0_out_expected_1 diff --git a/tester/test_0/run_check.transcript b/tester/test_0/run_check.transcript new file mode 100644 index 0000000..08b3fe9 --- /dev/null +++ b/tester/test_0/run_check.transcript @@ -0,0 +1,38 @@ +# due to file dates, the second check will find differences in file date pairs +# +# pass 1 +# Yes—this is a clean PASS ✅ +# started clean, +# ran Man_In_Grey once → all three outputs appeared with expected content, +# snapshotted as stage_test_0_out_expected_0. +# +# pass 2 +# Yes—this is a clean PASS ✅ +# web/nginx.conf and unbound_conf were displaced on the 2nd run → timestamped backups created, new files written. +# +# net/unbound.conf shows no backup, which is correct because that config does delete then copy (so overwrite without keeping a prior version). If you want a backup there too, switch that entry from delete→displace. + + +2025-09-20 16:12:28Z [Man_In_Grey:tester] Thomas-developer@StanleyPark +§/home/Thomas/subu_data/developer/project/active/Man_In_Grey/tester/test_0§ +> ./run_check ++ ./clean_out ++ find stage_test_0_out -type f -exec rm -f '{}' ';' ++ echo 'first run' +first run ++ ./run ++ /home/Thomas/subu_data/developer/project/Linux/Man_In_Grey/release/shell/Man_In_Grey --stage stage_test_0 ++ diff -qr stage_test_0_out stage_test_0_out_expected_0 ++ echo 'second run' +second run ++ ./run ++ /home/Thomas/subu_data/developer/project/Linux/Man_In_Grey/release/shell/Man_In_Grey --stage stage_test_0 ++ diff -qr stage_test_0_out stage_test_0_out_expected_1 +Only in stage_test_0_out_expected_1: unbound_conf.20250920T160648Z +Only in stage_test_0_out: unbound_conf.20250920T161232Z +Only in stage_test_0_out_expected_1/web: nginx.conf.20250920T160648Z +Only in stage_test_0_out/web: nginx.conf.20250920T161233Z + +2025-09-20 16:12:33Z [Man_In_Grey:tester] Thomas-developer@StanleyPark +§/home/Thomas/subu_data/developer/project/active/Man_In_Grey/tester/test_0§ +> diff --git a/tester/test_0/stage_test_0_out/.githolder b/tester/test_0/stage_test_0_out/.githolder deleted file mode 100644 index e69de29..0000000 diff --git a/tester/test_0/stage_test_0_out/net/unbound.conf b/tester/test_0/stage_test_0_out/net/unbound.conf new file mode 100644 index 0000000..b044194 --- /dev/null +++ b/tester/test_0/stage_test_0_out/net/unbound.conf @@ -0,0 +1,2 @@ +server: + verbosity: 1 diff --git a/tester/test_0/stage_test_0_out/unbound_conf b/tester/test_0/stage_test_0_out/unbound_conf new file mode 100644 index 0000000..d52f868 --- /dev/null +++ b/tester/test_0/stage_test_0_out/unbound_conf @@ -0,0 +1,2 @@ +server: + do-ip6: no diff --git a/tester/test_0/stage_test_0_out/unbound_conf.20250920T161232Z b/tester/test_0/stage_test_0_out/unbound_conf.20250920T161232Z new file mode 100644 index 0000000..d52f868 --- /dev/null +++ b/tester/test_0/stage_test_0_out/unbound_conf.20250920T161232Z @@ -0,0 +1,2 @@ +server: + do-ip6: no diff --git a/tester/test_0/stage_test_0_out/web/nginx.conf b/tester/test_0/stage_test_0_out/web/nginx.conf new file mode 100644 index 0000000..f48355c --- /dev/null +++ b/tester/test_0/stage_test_0_out/web/nginx.conf @@ -0,0 +1,2 @@ +events {} +http { server { listen 8080; } } diff --git a/tester/test_0/stage_test_0_out/web/nginx.conf.20250920T161233Z b/tester/test_0/stage_test_0_out/web/nginx.conf.20250920T161233Z new file mode 100644 index 0000000..f48355c --- /dev/null +++ b/tester/test_0/stage_test_0_out/web/nginx.conf.20250920T161233Z @@ -0,0 +1,2 @@ +events {} +http { server { listen 8080; } } diff --git a/tester/test_0/stage_test_0_out_expected_0/net/unbound.conf b/tester/test_0/stage_test_0_out_expected_0/net/unbound.conf new file mode 100644 index 0000000..b044194 --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_0/net/unbound.conf @@ -0,0 +1,2 @@ +server: + verbosity: 1 diff --git a/tester/test_0/stage_test_0_out_expected_0/unbound_conf b/tester/test_0/stage_test_0_out_expected_0/unbound_conf new file mode 100644 index 0000000..d52f868 --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_0/unbound_conf @@ -0,0 +1,2 @@ +server: + do-ip6: no diff --git a/tester/test_0/stage_test_0_out_expected_0/web/nginx.conf b/tester/test_0/stage_test_0_out_expected_0/web/nginx.conf new file mode 100644 index 0000000..f48355c --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_0/web/nginx.conf @@ -0,0 +1,2 @@ +events {} +http { server { listen 8080; } } diff --git a/tester/test_0/stage_test_0_out_expected_1/net/unbound.conf b/tester/test_0/stage_test_0_out_expected_1/net/unbound.conf new file mode 100644 index 0000000..b044194 --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_1/net/unbound.conf @@ -0,0 +1,2 @@ +server: + verbosity: 1 diff --git a/tester/test_0/stage_test_0_out_expected_1/unbound_conf b/tester/test_0/stage_test_0_out_expected_1/unbound_conf new file mode 100644 index 0000000..d52f868 --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_1/unbound_conf @@ -0,0 +1,2 @@ +server: + do-ip6: no diff --git a/tester/test_0/stage_test_0_out_expected_1/unbound_conf.20250920T160648Z b/tester/test_0/stage_test_0_out_expected_1/unbound_conf.20250920T160648Z new file mode 100644 index 0000000..d52f868 --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_1/unbound_conf.20250920T160648Z @@ -0,0 +1,2 @@ +server: + do-ip6: no diff --git a/tester/test_0/stage_test_0_out_expected_1/web/nginx.conf b/tester/test_0/stage_test_0_out_expected_1/web/nginx.conf new file mode 100644 index 0000000..f48355c --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_1/web/nginx.conf @@ -0,0 +1,2 @@ +events {} +http { server { listen 8080; } } diff --git a/tester/test_0/stage_test_0_out_expected_1/web/nginx.conf.20250920T160648Z b/tester/test_0/stage_test_0_out_expected_1/web/nginx.conf.20250920T160648Z new file mode 100644 index 0000000..f48355c --- /dev/null +++ b/tester/test_0/stage_test_0_out_expected_1/web/nginx.conf.20250920T160648Z @@ -0,0 +1,2 @@ +events {} +http { server { listen 8080; } } diff --git a/tester/tool/env b/tester/tool/env index de541e8..a6f9a98 100644 --- a/tester/tool/env +++ b/tester/tool/env @@ -22,7 +22,7 @@ case "$_arch_raw" in esac export ARCH="$_arch" -# Handy convenience paths (optional) -export REL_SHELL="$RELEASE/shell" -export REL_PY="$RELEASE/python3" -export REL_ARCH="$RELEASE/$ARCH" +# important release directories the main program call is in RELEASE_SHELL +export RELEASE_SHELL="$RELEASE/shell" +export RELEASE_PY="$RELEASE/python3" +export RELEASE_ARCH="$RELEASE/$ARCH" diff --git a/tool_shared/document/install_Python.org b/tool_shared/document/install_Python.org index 4725c04..2cdede9 100644 --- a/tool_shared/document/install_Python.org +++ b/tool_shared/document/install_Python.org @@ -58,6 +58,13 @@ Ensure the following: The binary should exist and report a working Python interpreter when run. +6. Install cbor2 + + which python3 + python3 -m pip install --upgrade pip + python3 -m pip install cbor2 + + * Notes - The virtual environment is deliberately named =Python=, not =venv=, to reflect its role as a shared system component.