Files
pi-extensions/docs/adr/0009-time-bare-form-unwrap.md
SikongJueluo e2719f9f11 feat(pi-permission-inner-cmd): recognize bare time wrapper as transparent
- add time handler that unwraps the bare reserved-word form time <command> and re-evaluates the full de-wrapped compound like timeout (ADR 0009)
- defer fail-closed on dash-leading modifiers, bare time, and nested wrappers in both directions
- generalize isRecognizedWrapper to timeout and time, and classifyWrapper to recognized/unsupported/other with a wrapper name
- rename defer events to inner_cmd.nested_wrapper and inner_cmd.unsupported_wrapper_syntax with a wrapper field
- extract stripWrapperUnit into handlers/strip.ts for shared use
2026-08-23 16:48:31 +08:00

64 lines
2.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: accepted
---
# The bare `time` wrapper unwraps like `timeout` (ADR 0009)
`pi-permission-inner-cmd` recognized only `timeout <duration> <command>`
(ADR 0001). `time <command>` poses the same authorization question — a
wrapper around an inner command — and appears routinely in agent Bash calls
(`time pnpm build`). A wrapper qualifies for unwrapping only when its
modifier args are transparent (CONTEXT.md, Wrappers).
## Decision
Recognize exactly the bare reserved-word form:
```regex
^time[ \t]+(?![-\s])(.+)$
```
It is transparent: `time` runs the inner command unchanged and only adds
timing, so the `timeout` unwrap pipeline applies verbatim — strip the wrapper
from the FULL command (scaffold included) and re-evaluate the whole de-wrapped
compound, so sibling commands stay under judgment. An inner `allow` allows,
anything else defers fail-closed.
Everything else that begins with `time` defers with
`inner_cmd.unsupported_wrapper_syntax`:
- **any dash-leading modifier** (`time -p ls`, `time -- ls`, `time -o FILE ls`).
The command string cannot distinguish the Bash reserved word (which accepts
only `-p`) from `/usr/bin/time` (whose `-o FILE` writes a file and `-v`
dumps environment-bearing stats), and the lookahead also blocks regex
backtracking from smuggling a leading space past a wide separator. A
modifier arg is therefore treated as potentially non-transparent until
individually proven otherwise.
- **a bare `time`** — it times the shell itself; there is no inner command to
re-evaluate.
`/usr/bin/time cmd` (full path) is not claimed by any handler and defers
silently like any unrecognized command.
## Generalizations that ride along
- `isRecognizedWrapper` now covers timeout ∪ time, so `time timeout 10 cmd`,
`timeout 10 time cmd`, and `time time cmd` all defer at most-one-level
nested wrappers (previously the check was timeout-only).
- The defer events generalize from `inner_cmd.nested_timeout` /
`inner_cmd.unsupported_timeout_syntax` to `inner_cmd.nested_wrapper` /
`inner_cmd.unsupported_wrapper_syntax`, each carrying a `wrapper:
"timeout" | "time"` field, so future wrapper handlers reuse the same event
names. Downstream analysis joins only on the decisive
`inner_cmd.allow|deny` markers and is unaffected.
## Consequences
- `time pnpm test` no longer reaches the human dialog / AI judge when the
inner command is already allowed.
- Handler precedence stays irrelevant: `timeout` and `time` prefixes are
disjoint, and both are tried before the claiming non-transparent handlers
(`env`, `xargs`).
- If `time -p` transparency is ever wanted, it is a one-line grammar change
plus tests — recorded here as deliberately out of scope for v0.1.