diff --git a/Directory.Build.props b/Directory.Build.props
index 675ef302c..620f70ac6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -459,8 +459,12 @@
canonical generation-token contract, and makes structured-record completion evidence
unambiguous. The engine adds the matching derived-entry and normalization controls. Additive
repair; no command, package, or member identity is removed. -->
+
- 0.75.1
+ 0.75.2
diff --git a/registry/repos.lock b/registry/repos.lock
index a157171f3..d9bc0913e 100644
--- a/registry/repos.lock
+++ b/registry/repos.lock
@@ -10,7 +10,7 @@ f7de20e04981ed72ce6bfe886a1776adb78b3926c2a83f315069a3ce8e4fa3f3 .claude/skills
bc040e2ba93b490a31f868497e46eeb2c6a2eea6f27a3fc90bb21361887aefe0 .claude/skills/cross-repo-coordination
7bee25b83b422cf0654178a741de4e90a424c24931ae119004843c5fc74b784f .claude/skills/intra-repo-parallel-work
44c2aacecbdbbb4f80f7b2f07379ec6ee38502ac84b5c991ea472b09baba29f3 .claude/skills/pnext-item
-d8a2c754a16bc298843297dc0b6cc49e7d2502446d9d4a9880bee7aabb2b34f3 scripts/fsgg-coord
+a0689f0ba8cd0180fd1ca60a63bba55e93513732b36e099c541847336ddb3ccf scripts/fsgg-coord
e1f63cda5b499f4fdd4396626c0bac013c49019daf182336df8c7513d24918ba scripts/fsgg-coord-report
1f2a70607268d549aa59c56e612e654e52021e9b7015e26c8fdaa97a4a3a869e scripts/lib/args.sh
fbdedf5e5bb4f85ef048807a430218001f489172210f87acd9bf8b3fbbca0330 scripts/lib/roots.sh
diff --git a/scripts/fsgg-coord b/scripts/fsgg-coord
index 1b2f8e9dd..a6901902e 100755
--- a/scripts/fsgg-coord
+++ b/scripts/fsgg-coord
@@ -22,8 +22,9 @@
# written here today would simply start rotting tomorrow. If you want the count, run `wc -l`.
#
# WHAT IT DOES. It resolves ONE engine and execs it, passing argv through unchanged and returning its exit
-# code unchanged. It is a transparent pipe — it adds no output of its own on the happy path and holds no
-# state. The resolution order is:
+# code unchanged. It preserves engine stdout and exit status and holds no state. Tier 4 additionally emits
+# one stable JSON resolution receipt on stderr, because its manifest pin and executed assembly can otherwise
+# diverge invisibly with caller cwd (#2908). The resolution order is:
#
# 1. THE EXPLICIT BIN — `FSGG_COORD_ENGINE_BIN`. An instruction. Honoured or REFUSED, never fallen
# back from: a caller who named an engine that is not there must be told, not
@@ -36,10 +37,10 @@
# therefore cannot prevent the coordination tool from being fixed."* So where a
# source build exists it is the AUTHORITY, and it outranks both packaged forms.
# 3. a global tool on PATH — `dotnet tool install -g`, or any binary an operator put on PATH.
-# 4. a LOCAL tool manifest — the receivers' shape: `.config/dotnet-tools.json` + `dotnet tool run`. A
-# restored local tool is NOT on PATH (it answers only to `dotnet tool run`),
-# so a manifest that DECLARES the engine but has not been restored is restored
-# here — a manifest is a wish until `dotnet tool restore` grants it (#655).
+# 4. a LOCAL tool manifest — the receivers' shape: `.config/dotnet-tools.json` + its exact restored
+# assembly. A restored local tool is NOT on PATH, so the wrapper-root manifest
+# is resolved through the SDK cache and that assembly is exec'd from the caller's
+# cwd. A declaration not restored yet is restored here (#655, #2908).
#
# THE NUMBERS MOVED, AND #1018 IS WHY (old → new: 4 → 2, 2 → 3, 3 → 4; tier 1 is unchanged). The source
# build used to be LAST, beneath both packaged forms — an order "lifted verbatim from bash's proven
@@ -330,23 +331,53 @@ if command -v fsgg-coord-engine >/dev/null 2>&1; then
exec fsgg-coord-engine "$@"
fi
-if [ -n "$TOP" ]; then
+WRAPPER_TOP="$(git -C "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" rev-parse --show-toplevel 2>/dev/null || true)"
+if [ -n "$WRAPPER_TOP" ]; then
# TIER 4 — THE RECEIVERS' SHAPE. Check the MANIFEST before probing `dotnet tool run` blindly — a `run` on
# a repo with no manifest is a slow, noisy failure, and `.github` (whose source build is tier 2 above) has
# no root manifest at all.
- MANIFEST="$TOP/.config/dotnet-tools.json"
+ MANIFEST="$WRAPPER_TOP/.config/dotnet-tools.json"
if [ -f "$MANIFEST" ] && command -v dotnet >/dev/null 2>&1 \
&& jq -e '.tools | to_entries[] | select(.value.commands // [] | index("fsgg-coord-engine"))' \
>/dev/null 2>&1 <"$MANIFEST"; then
- # A MANIFEST IS A DECLARATION, NOT AN INSTALLATION (#655). If the tool is not yet restored, `dotnet tool
- # run` prints a restore hint and exits 1 — so restore it once (bookkeeping, in a subshell), then exec.
- # Asking a worker to restore has a measured hit rate of zero; do it for them.
- # `--` here is PROPHYLACTIC, not a fix: measured on dotnet 10.0.302, `tool run` does NOT consume
- # `--version` (unlike `--help`/`-h`, which it does — see the exec below, #1029). It is separated anyway
- # because this probe FAILS OPEN if that ever changes: a consumed `--version` would answer 0 from dotnet
- # itself, the shim would read "already restored" and skip the restore, and the exec would then fail with
- # the very hint this branch exists to pre-empt. One separator costs nothing and removes the whole class.
- if ! ( cd "$TOP" && dotnet tool run fsgg-coord-engine -- --version ) >/dev/null 2>&1; then
+ # `dotnet tool run` has NO explicit-manifest option: it always searches from the process cwd. Calling it
+ # from $WRAPPER_TOP selects the right manifest but silently gives repository-sensitive commands the wrong
+ # cwd; calling it here preserves command scope but can select a different checkout's pin (.github#2908).
+ # Keep those two decisions separate. The manifest chooses one package+pin, the SDK's resolver cache names
+ # that restored pin's exact assembly, and `dotnet ` preserves the caller's cwd.
+ manifest_entry="$(jq -er '
+ [.tools | to_entries[]
+ | select(.value.commands // [] | index("fsgg-coord-engine"))
+ | [.key, .value.version]] as $entries
+ | if ($entries | length) == 1
+ then $entries[0] | @tsv
+ else error("expected exactly one fsgg-coord-engine declaration")
+ end' "$MANIFEST" 2>/dev/null)" \
+ || die "the engine manifest is unreadable or ambiguous: $MANIFEST"
+ TOOL_PACKAGE="${manifest_entry%% *}"
+ TOOL_PIN="${manifest_entry#* }"
+ TOOL_CACHE_KEY="$(printf '%s' "$TOOL_PACKAGE" | tr '[:upper:]' '[:lower:]')"
+ TOOL_RESOLVER_ROOT="${DOTNET_CLI_HOME:-${HOME:-}}/.dotnet/toolResolverCache"
+
+ resolve_manifest_engine() {
+ local resolver_file candidate resolved=""
+ [ -n "${DOTNET_CLI_HOME:-${HOME:-}}" ] || return 1
+ for resolver_file in "$TOOL_RESOLVER_ROOT"/*/"$TOOL_CACHE_KEY"; do
+ [ -f "$resolver_file" ] || continue
+ candidate="$(jq -er --arg pin "$TOOL_PIN" '
+ [.[] | select(.Version == $pin and .Name == "fsgg-coord-engine") | .PathToExecutable]
+ | if length == 1 then .[0] else empty end' "$resolver_file" 2>/dev/null)" || continue
+ [ -n "$resolved" ] && [ "$resolved" != "$candidate" ] && return 1
+ resolved="$candidate"
+ done
+ [ -n "$resolved" ] && [ -f "$resolved" ] || return 1
+ printf '%s' "$resolved"
+ }
+
+ TOOL_ASSEMBLY="$(resolve_manifest_engine || true)"
+ # A MANIFEST IS A DECLARATION, NOT AN INSTALLATION (#655). If its exact package+pin does not resolve
+ # to a present assembly, restore THAT explicit manifest; never let restore rediscover from caller cwd.
+ if [ -z "$TOOL_ASSEMBLY" ]; then
# THE RESTORE'S OWN DIAGNOSIS IS THE EVIDENCE — DO NOT DISCARD IT AND THEN GUESS (#957).
#
# This was `>/dev/null 2>&1 || die "… the feed is unreachable or the version is unpublished"`:
@@ -358,7 +389,7 @@ if [ -n "$TOP" ]; then
# checks their network; told "the version is unpublished" they go read the release workflow.
# Neither is the remedy, and the real one was named nowhere. It cost the reporter a debugging
# pass on an otherwise-clean checkout.
- if ! restore_out="$( cd "$TOP" && dotnet tool restore 2>&1 )"; then
+ if ! restore_out="$(dotnet tool restore --tool-manifest "$MANIFEST" 2>&1)"; then
# A HINT IS KEYED OFF THE OUTPUT, NEVER ASSERTED BLIND — that asymmetry IS the fix. Output we
# do not recognise gets NO cause: the restore's own words are the finding, and inventing one on
# top of them is the bug being removed. The two original causes survive here, demoted from
@@ -397,29 +428,30 @@ if [ -n "$TOP" ]; then
The version declared in the manifest is not on the feed. This is the 'unpublished' case, claimed here
only because the restore reported it." ;;
esac
- die "the engine is declared in $MANIFEST but 'dotnet tool restore' failed:
+ die "the engine is declared in $MANIFEST (pin=$TOOL_PACKAGE@$TOOL_PIN) but 'dotnet tool restore --tool-manifest $MANIFEST' failed:
$restore_out$hint"
fi
+ TOOL_ASSEMBLY="$(resolve_manifest_engine || true)"
+ [ -n "$TOOL_ASSEMBLY" ] \
+ || die "the engine manifest $MANIFEST pins $TOOL_PACKAGE@$TOOL_PIN, but restore produced no unique present resolver-cache assembly"
fi
- # exec from the ORIGINAL cwd, not $TOP: `dotnet tool run` finds the manifest by walking up from here,
- # and the engine is cwd-sensitive (reap scopes to the checkout you stand in, #480; the git-remote repo
- # default reads it, #430). A cd to $TOP would silently rescope those commands.
- #
- # `--` IS LOAD-BEARING, AND ITS ABSENCE IS WHY THIS SHIM WAS NOT THE TRANSPARENT PIPE ITS HEADER
- # PROMISES (#1029). `dotnet tool run` has its OWN `-?, -h, --help` option, so without a separator it
- # CONSUMES those flags and answers for itself — the engine is never reached. Measured on dotnet
- # 10.0.302, and it is not position-dependent (`… fsgg-coord-engine next --help` is eaten too):
- #
- # dotnet tool run fsgg-coord-engine --help -> dotnet's own help, EXIT 0
- # dotnet tool run fsgg-coord-engine next --help -> dotnet's own help, EXIT 0
- # dotnet tool run fsgg-coord-engine -- next --help -> reaches the engine
- #
- # Exit 0 is what made it silent: the worker asked the coordination tool for help, got dotnet's, and
- # nothing reported a failure. THIS repo cannot reproduce it — `.github` builds from source and resolves
- # at tier 2, which is a real `exec` — so the only repo that owns the shim is the one repo where the bug
- # is invisible, and every RECEIVER got dotnet's help instead (ADR-0034 decision 2).
- exec dotnet tool run fsgg-coord-engine -- "$@"
+ RESOLVED_VERSION="$(dotnet "$TOOL_ASSEMBLY" --version 2>/dev/null || true)"
+ case "$RESOLVED_VERSION" in
+ "$TOOL_PIN"|"$TOOL_PIN.0") ;;
+ *) die "engine pin mismatch: manifest=$MANIFEST pin=$TOOL_PACKAGE@$TOOL_PIN resolvedAssembly=$TOOL_ASSEMBLY resolvedVersion=${RESOLVED_VERSION:-unreadable}" ;;
+ esac
+
+ # STDERR, never stdout: stdout is the engine's machine contract (`next` is one bare ref, JSON verbs are
+ # one JSON value). The receipt is itself one deterministic JSON object so paths containing spaces or
+ # quotes remain unambiguous, and it is emitted only after the exact assembly reports the pinned version.
+ jq -cn --arg manifest "$MANIFEST" --arg package "$TOOL_PACKAGE" --arg pin "$TOOL_PIN" --arg assembly "$TOOL_ASSEMBLY" \
+ '{schema:"fsgg.coord.engine-resolution/v1",tier:4,manifest:$manifest,pin:($package+"@"+$pin),resolvedAssembly:$assembly}' >&2 \
+ || die "could not render the tier-4 engine-resolution receipt for manifest=$MANIFEST pin=$TOOL_PACKAGE@$TOOL_PIN resolvedAssembly=$TOOL_ASSEMBLY"
+
+ # No `cd`: repository-sensitive engine behaviour still observes the caller's working directory. Direct
+ # assembly execution also keeps argv transparent without `dotnet tool run` consuming -h/--help (#1029).
+ exec dotnet "$TOOL_ASSEMBLY" "$@"
fi
fi
diff --git a/src/FS.GG.Coord.Cli/FS.GG.Coord.Cli.fsproj b/src/FS.GG.Coord.Cli/FS.GG.Coord.Cli.fsproj
index 4ec666c1a..6ccbf3713 100644
--- a/src/FS.GG.Coord.Cli/FS.GG.Coord.Cli.fsproj
+++ b/src/FS.GG.Coord.Cli/FS.GG.Coord.Cli.fsproj
@@ -130,14 +130,15 @@ bytes under 0.52.0 would make the same version identify different artifacts. Ado
coherent set cut from corrected source. All three partial sets are recorded in
`registry/dependencies.yml`.
- 0.75.1 — host-owned review waits and unambiguous completion evidence.
-
-WHAT MOVED, AND WHY IT IS A PATCH. Review queue entry now derives the current claim generation, PR
-head, review kind, and round through the engine rather than a caller-authored token. Completion accepts
-the required structured review record's canonical URL, comment id, or digest, normalizes it to the URL,
-and refuses prose-marker or ambiguous references before append. The packed Kit documents that same
-host-owned route while preserving the exact canonical generation-token contract. This is an additive
-repair with no removed command, package, member, or identity (.github#2859; parent .github#2903).
+ 0.75.2 — caller-CWD-independent tier-4 engine resolution.
+
+WHAT MOVED, AND WHY IT IS A PATCH. The receiver wrapper now resolves tier 4 from its own tool manifest
+rather than whichever manifest is discoverable from the caller's current directory. It restores that
+exact manifest when needed, verifies the pinned assembly's reported version, and executes the assembly
+directly while preserving caller cwd and argv. A successful production execution emits one stable JSON
+resolution receipt on stderr with the manifest path, package pin, and resolved assembly. This is an
+additive routing and observability repair with no removed command, package, member, or identity
+(.github#2908; parent .github#2903).
$(FsggStandingAdvisories)
diff --git a/tests/coord-engine-parity/shim.sh b/tests/coord-engine-parity/shim.sh
index b84c93955..3ca3bb52d 100755
--- a/tests/coord-engine-parity/shim.sh
+++ b/tests/coord-engine-parity/shim.sh
@@ -1513,87 +1513,95 @@ else
fi
rm -rf "$NOHEAD" "$D" "$DWT"
-# ---- 7. TIER 4 IS A PIPE TOO: `dotnet tool run` EATS --help (#1029) -------------------------------
+# ---- 7. TIER 4 BINDS THE WRAPPER MANIFEST WITHOUT CHANGING CALLER CWD (#2908, #1029) -------------
#
-# The shim's header promises a transparent pipe that "adds no output of its own" and passes "argv through
-# unchanged". On TIER 4 — the RECEIVERS' shape (`.config/dotnet-tools.json` + `dotnet tool run`) — it was
-# not one. `dotnet tool run` has its OWN `-?, -h, --help` option and CONSUMES those flags before the tool
-# is ever reached, answering with its own help and EXIT 0. So on every receiver repo a worker who asked the
-# coordination tool for help got dotnet's, and nothing reported a failure. `--help` is how a worker
-# discovers a verb or a flag (#891) — what was undeliverable was the whole usage, not one line of it.
-#
-# WHY IT SURVIVED IS STRUCTURAL, AND IT IS THIS FILE'S FAULT. `.github` builds the engine from source and
-# resolves at TIER 2 (ADR-0034 decision 2), which is a genuine `exec` and genuinely transparent — so the ONE
-# repo that owns the shim is the one repo where the bug cannot reproduce, and not one leg above drove tier 4
-# at all. Every tier the owner can reach was pinned; the tier only the receivers reach was not.
-#
-# WHY `dotnet` IS FAKED. Driving the real `dotnet tool run` needs the tool RESTORED from the auth-walled org
-# feed, which no leg here may depend on. So the fake emulates exactly ONE behaviour, MEASURED against the
-# real dotnet 10.0.302 rather than assumed — and nothing else:
-#
-# dotnet tool run fsgg-coord-engine --help -> dotnet's own help, exit 0 (eaten)
-# dotnet tool run fsgg-coord-engine next --help -> dotnet's own help, exit 0 (eaten; NOT positional)
-# dotnet tool run fsgg-coord-engine --version -> reaches the tool (NOT eaten)
-# dotnet tool run fsgg-coord-engine -- next --help -> reaches the tool
-#
-# Leg (a) needs no model at all: it asserts what the SHIM HANDED dotnet, which is the header's contract
-# stated as an argv. Legs (b)-(d) show why that matters, against a dotnet that eats what the real one eats.
-T4="$(mktemp -d)"; DOTNETDIR="$(mktemp -d)"; ARGV_LOG="$T4/argv.log"
+# A receiver wrapper can be invoked while the caller stands in another checkout. `dotnet tool run` has no
+# explicit-manifest option and searches from that cwd, so the old probe/restore (wrapper tree) and execution
+# (caller tree) could select different pins. This fixture gives BOTH trees a manifest and makes their versions
+# observably different. The fake models the production resolver cache written by `dotnet tool restore`, then
+# executes the exact assembly path the shim selected; it never talks to a feed or GitHub.
+T4_BASE="$(mktemp -d)"; T4="$T4_BASE/wrapper"; CALLER="$T4_BASE/caller"
+DOTNETDIR="$(mktemp -d)"; DOTNET_HOME="$(mktemp -d)"; ARGV_LOG="$T4_BASE/argv.log"
+mkdir -p "$T4/scripts" "$T4/.config" "$CALLER/.config" "$DOTNET_HOME/.dotnet/toolResolverCache/1" "$DOTNET_HOME/assemblies/0.71.0" "$DOTNET_HOME/assemblies/0.72.0"
( cd "$T4" && git init -q . ) >/dev/null 2>&1
-mkdir -p "$T4/.config"
+( cd "$CALLER" && git init -q . ) >/dev/null 2>&1
+cp "$SHIM" "$T4/scripts/fsgg-coord"; chmod +x "$T4/scripts/fsgg-coord"
+T4_SHIM="$T4/scripts/fsgg-coord"
cat >"$T4/.config/dotnet-tools.json" <<'JSON'
{ "version": 1, "isRoot": true,
- "tools": { "fs.gg.coord.cli": { "version": "0.3.0", "commands": ["fsgg-coord-engine"] } } }
+ "tools": { "fs.gg.coord.cli": { "version": "0.72.0", "commands": ["fsgg-coord-engine"] } } }
+JSON
+cat >"$CALLER/.config/dotnet-tools.json" <<'JSON'
+{ "version": 1, "isRoot": true,
+ "tools": { "fs.gg.coord.cli": { "version": "0.71.0", "commands": ["fsgg-coord-engine"] } } }
+JSON
+: >"$DOTNET_HOME/assemblies/0.71.0/fsgg-coord-engine.dll"
+: >"$DOTNET_HOME/assemblies/0.72.0/fsgg-coord-engine.dll"
+cat >"$DOTNET_HOME/.dotnet/toolResolverCache/1/fs.gg.coord.cli" <"$DOTNETDIR/dotnet" <<'SH'
#!/usr/bin/env bash
-# A fake `dotnet`. It emulates ONE measured behaviour of `dotnet tool run`: its own -?/-h/--help is
-# consumed BEFORE any `--`, and answered by dotnet itself with exit 0. Everything after a `--` is the
-# tool's. It NEVER talks to GitHub — a leg that can reach the real board is the bug, not the test for it.
-if [ "${1:-}" = "tool" ] && [ "${2:-}" = "run" ]; then
- shift 2
- printf '%s\n' "$*" >>"$ARGV_LOG_PATH"
- shift # the tool name
- for a in "$@"; do
- case "$a" in
- --) break ;;
- -h|-\?|--help) echo "Description:"; echo " Run a local tool."; exit 0 ;;
- esac
- done
- [ "${1:-}" = "--" ] && shift
- echo "ENGINE RAN: $*"
+if [ "${1:-}" = "tool" ] && [ "${2:-}" = "restore" ]; then
+ printf 'RESTORE cwd=%s args=%s\n' "$PWD" "$*" >>"$ARGV_LOG_PATH"
+ cp "$RESTORED_CACHE_FIXTURE" "$DOTNET_CLI_HOME/.dotnet/toolResolverCache/1/fs.gg.coord.cli"
+ exit 0
+fi
+assembly="${1:-}"; shift || true
+printf 'EXEC assembly=%s cwd=%s args=%s\n' "$assembly" "$PWD" "$*" >>"$ARGV_LOG_PATH"
+pin="$(basename "$(dirname "$assembly")")"
+if [ "${1:-}" = "--version" ]; then
+ printf '%s\n' "${FORCE_RESOLVED_VERSION:-$pin.0}"
exit 0
fi
-echo "fake dotnet: unhandled $*" >&2; exit 64
+printf 'ENGINE RAN cwd=%s args=%s\n' "$PWD" "$*"
+exit 0
SH
chmod +x "$DOTNETDIR/dotnet"
-t4run() { ( cd "$T4" && env -u FSGG_COORD_ENGINE_BIN ARGV_LOG_PATH="$ARGV_LOG" PATH="$DOTNETDIR:$PATH" "$SHIM" "$@" 2>&1 ); }
+t4run() { ( cd "$CALLER" && env -u FSGG_COORD_ENGINE_BIN DOTNET_CLI_HOME="$DOTNET_HOME" ARGV_LOG_PATH="$ARGV_LOG" PATH="$DOTNETDIR:/usr/bin:/bin" "$T4_SHIM" "$@" 2>&1 ); }
-# a. THE CONTRACT, AS AN ARGV. What the shim hands dotnet must separate the caller's argv from dotnet's own
-# options. This leg models nothing about dotnet — it reads what was handed over.
+# a. POSITIVE + INVERSION: wrapper-root 0.72 wins over caller-root 0.71, while the engine still runs in
+# CALLER. Resolution evidence must come from the copied PRODUCTION wrapper, not from the fake engine:
+# the fake deliberately prints only cwd and argv.
: >"$ARGV_LOG"; out="$(t4run next --help)"; rc=$?
-handed="$(tail -1 "$ARGV_LOG" 2>/dev/null)"
-[ "$handed" = "fsgg-coord-engine -- next --help" ] \
- && ok "#1029: tier 4 hands dotnet a '--' separator, so the caller's argv is dotnet's tool args, not dotnet's own" \
- || bad "#1029: the shim must separate the caller's argv from dotnet's options" "handed: $handed"
-
-# b. AND SO THE ENGINE ANSWERS. The same call, against a dotnet that eats what the real one eats.
-{ [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q 'ENGINE RAN: next --help'; } \
- && ok "#1029: ...so '--help' reaches the ENGINE on a receiver — the pipe is transparent at tier 4" \
- || bad "#1029: --help must reach the engine through tier 4" "rc=$rc out=$out"
-
-# c. THE NEGATIVE, and it is the whole bug: dotnet must not be the thing that answers. Exit 0 is what made
-# this silent — a wrong answer that reported success.
-printf '%s' "$out" | grep -q 'Run a local tool' \
- && bad "#1029: dotnet answered the help request — the shim is not a pipe at tier 4" "out: $out" \
- || ok "#1029: ...and dotnet does NOT answer for the engine (no 'Run a local tool', which exited 0 and read as success)"
-
-# d. `-h` IS THE SAME OPTION, and a fix that only separated `--help` would leave it eaten.
-: >"$ARGV_LOG"; out="$(t4run -h)"
-{ printf '%s' "$out" | grep -q 'ENGINE RAN: -h' && ! printf '%s' "$out" | grep -q 'Run a local tool'; } \
- && ok "#1029: '-h' reaches the engine too — dotnet's short form is separated by the same '--'" \
- || bad "#1029: -h must reach the engine as well" "out: $out"
-rm -rf "$T4" "$DOTNETDIR"
+resolution="$(jq -Rrc 'fromjson? | select(.schema == "fsgg.coord.engine-resolution/v1")' <<<"$out" 2>/dev/null || true)"
+{ [ "$rc" -eq 0 ] \
+ && jq -e --arg manifest "$T4/.config/dotnet-tools.json" --arg assembly "$DOTNET_HOME/assemblies/0.72.0/fsgg-coord-engine.dll" \
+ '.schema == "fsgg.coord.engine-resolution/v1" and .tier == 4 and .manifest == $manifest and .pin == "fs.gg.coord.cli@0.72.0" and .resolvedAssembly == $assembly' <<<"$resolution" >/dev/null \
+ && printf '%s' "$out" | grep -Fq "ENGINE RAN cwd=$CALLER args=next --help"; } \
+ && ok ".github#2908: production tier 4 reports wrapper manifest/pin/assembly while the engine preserves caller cwd and argv" \
+ || bad ".github#2908: wrapper manifest selection and command working context must be separate" "rc=$rc out=$out log=$(cat "$ARGV_LOG")"
+
+grep -q 'fs.gg.coord.cli@0.71.0' <<<"$out" \
+ && bad ".github#2908 inversion: caller manifest 0.71.0 silently won" "out=$out" \
+ || ok ".github#2908 inversion: the conflicting CALLER manifest cannot select its 0.71.0 engine"
+
+# b. Direct assembly execution retains #1029: dotnet's tool-run parser never gets a chance to consume help.
+{ grep -q 'args=next --help' <<<"$out" && ! grep -q 'tool run' "$ARGV_LOG"; } \
+ && ok "#1029: tier 4 remains an argv-transparent pipe; --help reaches the engine, not dotnet tool run" \
+ || bad "#1029: direct tier-4 execution must preserve engine argv" "out=$out log=$(cat "$ARGV_LOG")"
+
+# c. A missing exact wrapper pin restores the EXPLICIT wrapper manifest even though cwd carries another.
+RESTORED_CACHE="$T4_BASE/restored-cache.json"
+cp "$DOTNET_HOME/.dotnet/toolResolverCache/1/fs.gg.coord.cli" "$RESTORED_CACHE"
+jq '[.[] | select(.Version != "0.72.0")]' "$RESTORED_CACHE" >"$DOTNET_HOME/.dotnet/toolResolverCache/1/fs.gg.coord.cli"
+: >"$ARGV_LOG"
+out="$(RESTORED_CACHE_FIXTURE="$RESTORED_CACHE" t4run budget)"; rc=$?
+{ [ "$rc" -eq 0 ] && grep -Fq "tool restore --tool-manifest $T4/.config/dotnet-tools.json" "$ARGV_LOG" \
+ && grep -q '"pin":"fs.gg.coord.cli@0.72.0"' <<<"$out"; } \
+ && ok ".github#2908: restore is explicitly bound to the wrapper manifest, then that exact pin executes" \
+ || bad ".github#2908: restore must not rediscover the caller manifest" "rc=$rc out=$out log=$(cat "$ARGV_LOG")"
+
+# d. A resolver record whose assembly reports another version REFUSES and names all three authorities.
+: >"$ARGV_LOG"
+out="$(FORCE_RESOLVED_VERSION=0.71.0.0 t4run budget)"; rc=$?
+{ [ "$rc" -eq 69 ] && printf '%s' "$out" | grep -Fq "manifest=$T4/.config/dotnet-tools.json pin=fs.gg.coord.cli@0.72.0" \
+ && printf '%s' "$out" | grep -Fq "resolvedAssembly=$DOTNET_HOME/assemblies/0.72.0/fsgg-coord-engine.dll resolvedVersion=0.71.0.0"; } \
+ && ok ".github#2908 inversion: a pin/resolved-version mismatch fails closed with manifest, pin and assembly evidence" \
+ || bad ".github#2908: a wrong resolved assembly must never run silently" "rc=$rc out=$out log=$(cat "$ARGV_LOG")"
+
+rm -rf "$T4_BASE" "$DOTNETDIR" "$DOTNET_HOME"
echo
total=$((pass+failcount))