Package
pi-permission-system
What do you want to change?
A wrapper that only changes how the same visible command runs — time, timeout, nice, stdbuf, setsid — should inherit the inner command's verdict outright, instead of being floored to <indirection-bash-wrapper> unless the inner command happens to be a pure reader.
Today at 5ba2ba5e (33.0.5):
time pnpm run lint >/tmp/lintout.txt 2>&1; echo "lint rc=$?"; tail -3 /tmp/lintout.txt
asks with matchedPattern: <indirection-bash-wrapper>, while the same command without time resolves by its own rules. The exemption that would let it through exists — resolveWrapperUnit (src/handlers/gates/bash-command.ts) already resolves executedUnit on the bash surface when floorExemption holds — but isTransparentWrapper (src/access-intent/bash/wrapper-analysis.ts) admits a unit only when the inner head word is in PURE_READER_CORE and the unit has no output redirect (ADR 0013 §11, #803). pnpm is argument-dependent by design, so time pnpm … fails the bar; test/access-intent/bash/wrapper-analysis.test.ts pins it as ["time pnpm test", "the inner command is not in the core"].
Why?
INDIRECTION_WRAPPER_NAMES conflates two classes that #490 floored uniformly:
| Class |
Members |
What the wrapper changes |
| Changes what runs, as whom, or with which operands |
sudo, doas, env, xargs, parallel, rush, rust-parallel, find -exec, fd -x, watch |
privilege, environment (env PATH=… git), an argument feed the enumerator cannot see, repetition |
| Changes only how the same visible command runs |
time, timeout, nice, stdbuf, setsid |
timing, kill deadline, scheduling, buffering, session |
The floor's reason (#490) is that a wrapper "hides the command that should be gated". For the second class that is false whatever the inner command's effect: every operand is on the command line, and the wrapper adds no privilege, no environment, and no argument feed. §11 keys transparency on the inner command (argument-independence); this is a second, wrapper-keyed clause — still package-audited per wrapper, so it does not widen to user declarations, which #880 deliberately keeps outside the floor.
With the strides made on compound-command opacity, these two are now the wrappers I most often approve by hand for a command that would have passed on its own.
How? (optional)
- Split the second class out of
INDIRECTION_WRAPPER_NAMES into its own set (or tag), and let isTransparentWrapper admit a unit whose outermost wrapper is in it whenever unwrapIndirection peels cleanly (no opaque payload, layers ≥ 1) — the inner verdict is then whatever resolveWrapperUnit already computes.
A nested wrapper from the first class (time sudo …) stops the peel at sudo, which keeps its floor.
time: the shell keyword takes only -p, but wrapperName basenames /usr/bin/time, whose -o/--output/-a/--append write a file — refuse the exemption when one is present.
- The
writesViaRedirect refusal exists because the §11 exemption classifies the unit as read. This clause inherits the inner verdict instead, and the redirect destination is gated by the path surfaces exactly as for the unwrapped command — so I expect it not to apply here, but the plan should verify that redirect analysis on a wrapper unit is independent of the floor before dropping it.
nohup (writes nohup.out only when stdout is a tty) and flock (creates its lock-file operand) stay floored; either can be revisited separately.
- The metamorphic pin in
test/handlers/gates/bash-command-metamorphic.test.ts (time ${cmd} never loosens the verdict) should keep holding, since the unit's verdict becomes exactly the unwrapped command's.
- ADR 0013 §11 gains the second clause;
docs/architecture/architecture.md's wrapper-analysis.ts entry and the <indirection-bash-wrapper> description in docs/configuration.md follow.
Package
pi-permission-system
What do you want to change?
A wrapper that only changes how the same visible command runs —
time,timeout,nice,stdbuf,setsid— should inherit the inner command's verdict outright, instead of being floored to<indirection-bash-wrapper>unless the inner command happens to be a pure reader.Today at
5ba2ba5e(33.0.5):asks with
matchedPattern: <indirection-bash-wrapper>, while the same command withouttimeresolves by its own rules. The exemption that would let it through exists —resolveWrapperUnit(src/handlers/gates/bash-command.ts) already resolvesexecutedUniton the bash surface whenfloorExemptionholds — butisTransparentWrapper(src/access-intent/bash/wrapper-analysis.ts) admits a unit only when the inner head word is inPURE_READER_COREand the unit has no output redirect (ADR 0013 §11, #803).pnpmis argument-dependent by design, sotime pnpm …fails the bar;test/access-intent/bash/wrapper-analysis.test.tspins it as["time pnpm test", "the inner command is not in the core"].Why?
INDIRECTION_WRAPPER_NAMESconflates two classes that #490 floored uniformly:sudo,doas,env,xargs,parallel,rush,rust-parallel,find -exec,fd -x,watchenv PATH=… git), an argument feed the enumerator cannot see, repetitiontime,timeout,nice,stdbuf,setsidThe floor's reason (#490) is that a wrapper "hides the command that should be gated". For the second class that is false whatever the inner command's effect: every operand is on the command line, and the wrapper adds no privilege, no environment, and no argument feed. §11 keys transparency on the inner command (argument-independence); this is a second, wrapper-keyed clause — still package-audited per wrapper, so it does not widen to user declarations, which #880 deliberately keeps outside the floor.
With the strides made on compound-command opacity, these two are now the wrappers I most often approve by hand for a command that would have passed on its own.
How? (optional)
INDIRECTION_WRAPPER_NAMESinto its own set (or tag), and letisTransparentWrapperadmit a unit whose outermost wrapper is in it wheneverunwrapIndirectionpeels cleanly (no opaque payload,layers ≥ 1) — the inner verdict is then whateverresolveWrapperUnitalready computes.A nested wrapper from the first class (
time sudo …) stops the peel atsudo, which keeps its floor.time: the shell keyword takes only-p, butwrapperNamebasenames/usr/bin/time, whose-o/--output/-a/--appendwrite a file — refuse the exemption when one is present.writesViaRedirectrefusal exists because the §11 exemption classifies the unit as read. This clause inherits the inner verdict instead, and the redirect destination is gated by the path surfaces exactly as for the unwrapped command — so I expect it not to apply here, but the plan should verify that redirect analysis on a wrapper unit is independent of the floor before dropping it.nohup(writesnohup.outonly when stdout is a tty) andflock(creates its lock-file operand) stay floored; either can be revisited separately.test/handlers/gates/bash-command-metamorphic.test.ts(time ${cmd}never loosens the verdict) should keep holding, since the unit's verdict becomes exactly the unwrapped command's.docs/architecture/architecture.md'swrapper-analysis.tsentry and the<indirection-bash-wrapper>description indocs/configuration.mdfollow.