You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After this PR a slim Android device also has a known set of background packages disabled. The set ships with Simlock in named categories. android.slim.categories narrows it; omitted means every default category. Play Store, Chrome, and the setup wizard form the one category that is off unless named. Google Play Services is never disabled and a slim device always finishes booting. The pass runs once, before the clean baseline is captured, so a reclaimed device comes back slim with no extra boot. simlock events shows device.slimmed for the device. A package the image lacks is skipped and reported; a pass that cannot run leaves the device usable and not yet captured.
{
"android": {
"slim": {
"categories": ["search", "media"] // optional; omitted means every default category
}
}
}
Technical spec
Written before #172, which replaces slim.enabled, the full flag, and featureProfile. Revise this section against #172 before approval.
Modules touched
src/drivers/android/slim-packages.ts (new): versioned data, no driver logic. SLIM_PACKAGE_CATEGORIES (name, description, default, packages), NEVER_DISABLED, resolveSlimCategories(names?) (omitted means every category with default: true; an unknown name is returned separately, not thrown), packagesFor(categories), and slimSignature(categories), a hash over the resolved names and their packages, mirroring src/drivers/ios/slim-labels.ts. The categories and packages:
src/core/config.ts and src/contract/schemas.ts: android.slim.categories, optional, an array of non-empty strings, validated by shape only; the names are the driver's business.
src/daemon/main.ts: thread categories; bridge the Android driver's onSlimmed to device.slimmed with platform: "android" through emitSlimDiagnostic, which takes the platform from the fact; log onSlimSkipped as for iOS.
src/bus/index.ts: device.slimmed's platform becomes "ios" | "android". No other payload change.
src/drivers/android/index.ts:
AndroidSlimOptions.categories. At construction, resolve once; an unknown name is logged through a diagnostic and dropped. #currentConfigHash adds the signature when slim applies (ADR 0006 §3).
The pass runs in makeReady on the !state.baselineCaptured path only, after the first boot's readiness and before #captureBaseline: pm list packages, disable each resolved package that is present with pm disable-user --user 0 <package>, read back pm list packages -d, fire onSlimmed, then capture. Every command runs under the driver's command timeout and the whole pass under SLIM_PASS_TIMEOUT_MS measured from its start (ADR 0006 §8).
A package absent from the image, or one pm refuses, goes to unknownLabels and does not block the capture. A command that fails to run, or a pass over budget, skips the capture, reports onSlimSkipped, and returns the device with featureProfile: "reduced" (ADR 0006 §6). The baseline stays uncaptured so the next prepare boot retries.
No pass on a boot that restores the baseline, on a recover boot, or on a full device.
estimate({ operation: "boot" }) adds the pass budget when slim applies to the spec.
Package names are validated against ^[A-Za-z0-9_.]+$ at the one place they reach a shell argument (safety rule 10); the data test enforces the same shape.
docs/CONFIGURATION.md: the categories key, the category names and what each disables, the opt-in store category, what stops working.
docs/EVENTS.md and docs/internal/EVENTS.md: the device.slimmed row covers both platforms; on Android a label is a package name and the fact is read back from the guest's package state before the baseline is captured.
docs/internal/KNOWN-PITFALLS.md: the Android slim section gains the feature-loss list and the list-drift note.
e2e/slow-android-slim.test.ts: extended, see Tests.
Contract and event changes
device.slimmed: platform gains "android". Additive. Both EVENTS.md files updated in this change (events rule 8, documentation rule 4).
Config schema: android.slim.categories. No new operation or lease-request field.
docs/internal/agent-rules/events.md: rule 3 (the fact is read back before it is reported), rule 6 (additive payload), rule 8.
docs/internal/agent-rules/safety.md: rule 2 (no pass on a recovery boot), rule 10 (package names validated before the shell).
docs/internal/agent-rules/architecture.md: rule 2 (the core validates shape only, never a category name), rule 11 (the pass budget is measured from its start and does not reset per command), rule 12 (every exit of the pass leaves the baseline either captured or uncaptured, never half).
docs/internal/agent-rules/testing.md.
Tests
Every package in every category matches ^[A-Za-z0-9_.]+$ and none matches NEVER_DISABLED.
resolveSlimCategories(undefined) yields every category with default: true and not store; naming store includes it; an unknown name is reported and dropped without throwing.
slimSignature changes when a category's packages change and when the chosen categories change, and is stable across the order names are given in.
On a baseline-capturing boot with slim enabled, the driver lists installed packages, disables each resolved package the image carries, reads the disabled list back, and only then saves the baseline snapshot (scripted ProcessRunner, order asserted).
A package absent from the installed list is skipped, appears in unknownLabels, and the baseline is captured.
A pm disable-user that reports failure for a present package appears in unknownLabels and the baseline is captured.
An adb command that fails to run during the pass leaves the baseline uncaptured, reports onSlimSkipped, and makeReady still returns the device with featureProfile: "reduced"; the next prepare boot runs the pass again.
A pass whose commands never answer fails within SLIM_PASS_TIMEOUT_MS of its start, measured on the fake clock, and the boot still succeeds.
A boot that restores the clean baseline, a recover boot, and a full device's boot each run no pm command.
Changing android.slim.categories changes the baseline hash of a non-full device and not of a full device.
onSlimmed fires once per pass with the resolved categories, the disabled count as labelCount, the signature, unknownLabels, and a duration; the daemon bridges it to device.slimmed with platform: "android".
estimate({ operation: "boot" }) is larger by the pass budget when slim is enabled and the spec is not full, and unchanged otherwise.
An android.slim.categories that is not an array of non-empty strings is rejected at config load, naming the key.
simlock events documentation check: both EVENTS.md tables list device.slimmed for both platforms.
Slow lane, real emulator: after a slim lease, pm list packages -d on the guest contains every default-category package the image carries and com.google.android.gms is enabled; simlock events shows device.slimmed with platform: "android"; after release, the next lease of the same spec is granted after a device.reclaimed with strategy snapshot and the disabled set is unchanged; a --full lease of the same spec has no package disabled.
Done when
Every test above passes and pnpm check is green.
On a full device no pm command runs on any boot.
docs/CONFIGURATION.md lists the categories and what stops working; both EVENTS.md files describe the Android device.slimmed; docs/internal/KNOWN-PITFALLS.md has the Android section.
The slow lane passes on a machine with an Android SDK.
Part of #159.
Scope
After this PR a slim Android device also has a known set of background packages disabled. The set ships with Simlock in named categories.
android.slim.categoriesnarrows it; omitted means every default category. Play Store, Chrome, and the setup wizard form the one category that is off unless named. Google Play Services is never disabled and a slim device always finishes booting. The pass runs once, before the clean baseline is captured, so a reclaimed device comes back slim with no extra boot.simlock eventsshowsdevice.slimmedfor the device. A package the image lacks is skipped and reported; a pass that cannot run leaves the device usable and not yet captured.{ "android": { "slim": { "categories": ["search", "media"] // optional; omitted means every default category } } }Technical spec
Written before #172, which replaces
slim.enabled, thefullflag, andfeatureProfile. Revise this section against #172 before approval.Modules touched
src/drivers/android/slim-packages.ts(new): versioned data, no driver logic.SLIM_PACKAGE_CATEGORIES(name, description,default, packages),NEVER_DISABLED,resolveSlimCategories(names?)(omitted means every category withdefault: true; an unknown name is returned separately, not thrown),packagesFor(categories), andslimSignature(categories), a hash over the resolved names and their packages, mirroringsrc/drivers/ios/slim-labels.ts. The categories and packages:src/core/config.tsandsrc/contract/schemas.ts:android.slim.categories, optional, an array of non-empty strings, validated by shape only; the names are the driver's business.src/daemon/main.ts: threadcategories; bridge the Android driver'sonSlimmedtodevice.slimmedwithplatform: "android"throughemitSlimDiagnostic, which takes the platform from the fact; logonSlimSkippedas for iOS.src/bus/index.ts:device.slimmed'splatformbecomes"ios" | "android". No other payload change.src/drivers/android/index.ts:AndroidSlimOptions.categories. At construction, resolve once; an unknown name is logged through a diagnostic and dropped.#currentConfigHashadds the signature when slim applies (ADR 0006 §3).makeReadyon the!state.baselineCapturedpath only, after the first boot's readiness and before#captureBaseline:pm list packages, disable each resolved package that is present withpm disable-user --user 0 <package>, read backpm list packages -d, fireonSlimmed, then capture. Every command runs under the driver's command timeout and the whole pass underSLIM_PASS_TIMEOUT_MSmeasured from its start (ADR 0006 §8).pmrefuses, goes tounknownLabelsand does not block the capture. A command that fails to run, or a pass over budget, skips the capture, reportsonSlimSkipped, and returns the device withfeatureProfile: "reduced"(ADR 0006 §6). The baseline stays uncaptured so the nextprepareboot retries.recoverboot, or on afulldevice.estimate({ operation: "boot" })adds the pass budget when slim applies to the spec.^[A-Za-z0-9_.]+$at the one place they reach a shell argument (safety rule 10); the data test enforces the same shape.docs/CONFIGURATION.md: thecategorieskey, the category names and what each disables, the opt-instorecategory, what stops working.docs/EVENTS.mdanddocs/internal/EVENTS.md: thedevice.slimmedrow covers both platforms; on Android a label is a package name and the fact is read back from the guest's package state before the baseline is captured.docs/internal/KNOWN-PITFALLS.md: the Android slim section gains the feature-loss list and the list-drift note.e2e/slow-android-slim.test.ts: extended, see Tests.Contract and event changes
device.slimmed:platformgains"android". Additive. Both EVENTS.md files updated in this change (events rule 8, documentation rule 4).android.slim.categories. No new operation or lease-request field.Rules in play
docs/internal/adr/0006-opt-in-slim-android-emulators.md§2, §3, §6, §7, §8, §10.docs/internal/agent-rules/events.md: rule 3 (the fact is read back before it is reported), rule 6 (additive payload), rule 8.docs/internal/agent-rules/safety.md: rule 2 (no pass on a recovery boot), rule 10 (package names validated before the shell).docs/internal/agent-rules/architecture.md: rule 2 (the core validates shape only, never a category name), rule 11 (the pass budget is measured from its start and does not reset per command), rule 12 (every exit of the pass leaves the baseline either captured or uncaptured, never half).docs/internal/agent-rules/testing.md.Tests
^[A-Za-z0-9_.]+$and none matchesNEVER_DISABLED.resolveSlimCategories(undefined)yields every category withdefault: trueand notstore; namingstoreincludes it; an unknown name is reported and dropped without throwing.slimSignaturechanges when a category's packages change and when the chosen categories change, and is stable across the order names are given in.ProcessRunner, order asserted).unknownLabels, and the baseline is captured.pm disable-userthat reports failure for a present package appears inunknownLabelsand the baseline is captured.onSlimSkipped, andmakeReadystill returns the device withfeatureProfile: "reduced"; the nextprepareboot runs the pass again.SLIM_PASS_TIMEOUT_MSof its start, measured on the fake clock, and the boot still succeeds.recoverboot, and afulldevice's boot each run nopmcommand.android.slim.categorieschanges the baseline hash of a non-full device and not of afulldevice.onSlimmedfires once per pass with the resolved categories, the disabled count aslabelCount, the signature,unknownLabels, and a duration; the daemon bridges it todevice.slimmedwithplatform: "android".estimate({ operation: "boot" })is larger by the pass budget when slim is enabled and the spec is notfull, and unchanged otherwise.android.slim.categoriesthat is not an array of non-empty strings is rejected at config load, naming the key.simlock eventsdocumentation check: both EVENTS.md tables listdevice.slimmedfor both platforms.pm list packages -don the guest contains every default-category package the image carries andcom.google.android.gmsis enabled;simlock eventsshowsdevice.slimmedwithplatform: "android"; after release, the next lease of the same spec is granted after adevice.reclaimedwith strategysnapshotand the disabled set is unchanged; a--fulllease of the same spec has no package disabled.Done when
pnpm checkis green.pmcommand runs on any boot.docs/CONFIGURATION.mdlists the categories and what stops working; both EVENTS.md files describe the Androiddevice.slimmed;docs/internal/KNOWN-PITFALLS.mdhas the Android section.Out of scope
Depends on
Approval
Written by an agent.