fix(adb): scope package uninstall requests to user zero

Send shell pm uninstall --user 0 through the shared quoted command boundary. Align tool guidance and Evidence limitations with the selected request scope while preserving device refusal diagnostics.

Validation: two provider controls failed before the fix and pass afterward; three public CLI/MCP scenarios capture exact arguments and equivalent success facts; nineteen command/SDK contract checks, check:fast and docs:check pass. No real device, multi-user data preservation, vendor/APEX behavior or performance measurement. Refs #4673.
This commit is contained in:
morluto committed 2026-10-17 13:30:29 +08:00
1 parent c360cfcd6d
commit be0b2d138c
5 files changed
+130 -7

No files matched your search

+3 -3
View File
@@ -1515,7 +1515,7 @@ exit 1`,
it("uninstalls a package and reports the device's refusal for absent ones", async () => {
const { binary } = await writeAdbStub(
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
if [ "$1" = "-s" ] && [ "$3" = "uninstall" ]; then printf 'Success\\n'; exit 0; fi
if [ "$#" = 8 ] && [ "$1" = "-s" ] && [ "$3" = "shell" ] && [ "$4" = "pm" ] && [ "$5" = "uninstall" ] && [ "$6" = "--user" ] && [ "$7" = 0 ]; then printf 'Success\\n'; exit 0; fi
exit 1`,
);
const removed = await evidenceResult(providerOver(binary), {
@@ -1526,7 +1526,7 @@ exit 1`,
const { binary: refusing } = await writeAdbStub(
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
if [ "$1" = "-s" ] && [ "$3" = "uninstall" ]; then printf 'Failure [not installed for 0]\\n'; exit 0; fi
if [ "$#" = 8 ] && [ "$1" = "-s" ] && [ "$3" = "shell" ] && [ "$4" = "pm" ] && [ "$5" = "uninstall" ] && [ "$6" = "--user" ] && [ "$7" = 0 ]; then printf 'Failure [not installed for 0]\\n'; exit 0; fi
exit 1`,
);
const projected = await refusedBy(providerOver(refusing), {
@@ -1570,7 +1570,7 @@ exit 1`,
it("preserves the device's uninstall refusal when adb exits nonzero", async () => {
const { binary } = await writeAdbStub(
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
if [ "$1" = "-s" ] && [ "$3" = "uninstall" ]; then printf 'Failure [not installed for 0]\\n'; exit 1; fi
if [ "$#" = 8 ] && [ "$1" = "-s" ] && [ "$3" = "shell" ] && [ "$4" = "pm" ] && [ "$5" = "uninstall" ] && [ "$6" = "--user" ] && [ "$7" = 0 ]; then printf 'Failure [not installed for 0]\\n'; exit 1; fi
exit 1`,
);
const projected = await refusedBy(providerOver(binary), {
+9 -2
View File
@@ -2624,7 +2624,14 @@ export class AdbProvider {
operation,
input.serial,
selection.binary,
adbArguments(input.serial, ["uninstall", input.package]),
adbArguments(input.serial, [
"shell",
"pm",
"uninstall",
"--user",
"0",
input.package,
]),
{
timeoutMs: PACKAGE_LIST_TIMEOUT_MS,
maxOutputBytes: PULL_MAX_OUTPUT_BYTES,
@@ -2655,7 +2662,7 @@ export class AdbProvider {
provider,
{
limitations: [
"Uninstall removes the package and its data for user 0; keeping data (-k) is not exposed",
"Uninstall explicitly requests user 0 without data retention (-k); device enforcement is not independently verified",
],
},
),
+1 -1
View File
@@ -311,7 +311,7 @@ export const ADB_TOOL_CONTRACTS = [
name: "uninstall_adb_package",
...toolContractMetadata("uninstall_adb_package"),
description:
"Uninstall one package from the device (`adb uninstall`), removing its data for user 0. A device-mutating tool; absent packages report the device's own refusal reason.",
"Request package removal for user 0 (`adb shell pm uninstall --user 0`) without data retention. A device-mutating tool; absent packages report the device's own refusal reason.",
kind: "adb-provider",
inputSchema: adbInputSchemas.uninstall_adb_package,
outputSchema: evidenceResultOf(adbResultSchemas.uninstall_adb_package),