iOS Device Benchmarking with devicectl
Collective Library edition. This is the complete technical report. Private filesystem paths, internal run identifiers, campaign-control notes, and repository navigation were removed. Technical claims, code, measurements, evidence labels, citations, corrections, and falsification criteria are preserved.
September 6, 2026
This field guide covers the shell-operated tooling layer for running a Core ML measurement app on a physical iPhone: build and install, stage model data, launch and monitor, collect durable results, inspect compute plans, and recover crash evidence. It is reusable operating guidance, not a verdict on any the reference implementation model or certification campaign.
Executive Summary
devicectl device copy tois notcp. Firsthand implementation evidence: copying a file to a missing parent insideappDataContainercan exit without creating anything; copying the containing directory works. Verify every transfer.- Never use
--remove-existing-content truecasually. Current command help describes destination-directory cleanup, but firsthand implementation evidence shows it emptied the entire app data container. - In
device process launch, every token after the positional bundle identifier is applicationargv. Put everydevicectloption before that positional argument. --consoleconnects standard streams and waits for exit, but firsthand implementation evidence shows ordinary Swiftprintoutput can remain invisible because stdout is block-buffered. Emit critical progress to stderr and write final receipts toDocuments.- Crash evidence is separate from app data.
systemCrashLogsis a supported file-service domain and can contain both app and Core ML or ANE service failures. - An on-device
MLComputePlanis target-specific anticipated placement, not a trace of a completed prediction. ReaddeviceUsage(for:)andestimatedCost(of:), then use runtime telemetry for an execution claim. - A shell with no Xcode account signed in needs a manually managed development profile and matching development identity. This is firsthand the reference implementation behavior, not a claim that every CI signing setup must be manual.
- Community reports about fixed iPhone memory ceilings, JIT compilation spikes, AOT paging, background suspension, and re-signing are useful hypotheses. They remain labeled until reproduced on the target tuple.
Evidence Labels
- Current command help: reproduced with Xcode 26.6 and
devicectl518.33. Re-runxcrun devicectl help ...after changing Xcode. - Documented API: current Apple Core ML documentation retrieved through Context7 library
/websites/developer_apple_coreml. - Firsthand implementation evidence: reproduced by the local iOS harness workflow and recorded in the linked harness README or research brief.
- Community / unverified: synthesized from the raw report's public sources. Keep the recipe, but test it on the exact hardware, OS, Xcode, app, and model before relying on it.
1. Operating Model
A robust run has five explicit boundaries:
signed .app
-> install
-> stage one immutable input directory
-> launch one configuration
-> pull one result receipt and all relevant crash evidence
Treat the host, the app container, and system diagnostics as separate stores. A successful command invocation is not proof that a file arrived, an app completed, a model used the requested device, or no system daemon crashed.
For automation, prefer --json-output <path> where devicectl supports it. Current command help states that a user-provided JSON file is the only supported interface for programs consuming command output. Human-readable stdout is not a stable parser contract.
Use privacy-safe run identifiers in paths and receipts. Do not record device identifiers, device names, signing credentials, private audio paths, or transcript content.
2. Build and Install a Signed App
devicectl device install app installs an already signed .app; it does not solve signing or provisioning:
xcrun devicectl device install app \
--device "<device-id>" \
--json-output install.json \
"/path/to/Benchmark.app"
The placeholder device ID is operational input only. Do not persist it in a public receipt.
2.1. Manual signing when no Xcode account is signed in
Firsthand implementation evidence: automatic signing with -allowProvisioningUpdates failed with a "No Accounts" error when the build host had no account signed into Xcode. An Xcode-managed development profile was then rejected under manual signing. A non-Xcode-managed development profile that included the registered target device worked with manual signing:
xcodebuild \
-project Benchmark.xcodeproj \
-scheme Benchmark \
-configuration Release \
-destination "generic/platform=iOS" \
-derivedDataPath .build/xcode \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Development" \
PROVISIONING_PROFILE_SPECIFIER="<manual-profile-name>" \
build
The signing identity's private key and the provisioning profile must be available to the shell. The profile must authorize the app identifier, entitlements, team, and target device. Unlocking a keychain can fix private-key access errors, but it does not invent a missing Xcode account or repair a mismatched profile.
security find-identity -v -p codesigning
security cms -D -i "/path/to/profile.mobileprovision"
codesign -d --entitlements :- "/path/to/Benchmark.app"
codesign --verify --deep --strict --verbose=2 "/path/to/Benchmark.app"
Do not print keychain passwords, private keys, or full provisioning payloads into CI logs.
2.2. Archive and export
The raw report's archive/export route is worth keeping for CI systems that produce an archive. Xcode 26.6 marks export method development deprecated; use debugging.
xcodebuild archive \
-workspace Benchmark.xcworkspace \
-scheme Benchmark \
-destination "generic/platform=iOS" \
-archivePath .build/Benchmark.xcarchive \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Development" \
PROVISIONING_PROFILE_SPECIFIER="<manual-profile-name>"
xcodebuild -exportArchive \
-archivePath .build/Benchmark.xcarchive \
-exportPath .build/export \
-exportOptionsPlist ExportOptions.plist
A minimal current export configuration can name the profile rather than embedding private identifiers:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>debugging</string>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Apple Development</string>
<key>provisioningProfiles</key>
<dict>
<key>com.example.benchmark</key>
<string>Benchmark Development</string>
</dict>
</dict>
</plist>
Community / unverified: some headless setups require provisioning profiles to be installed under ~/Library/MobileDevice/Provisioning Profiles/ using the profile's internal identifier as the filename. Other tooling accepts a profile name or identifier in provisioningProfiles. Inspect the active Xcode version's xcodebuild -help; do not assume one naming convention is universal.
2.3. Re-signing and signing automation
Community / worth trying: re-signing a prebuilt app or IPA can work when the executable, embedded frameworks, entitlements, app identifier, and profile all agree. Sign nested code before the containing app:
for framework in Payload/Benchmark.app/Frameworks/*.framework; do
codesign --force --sign "<development-identity>" "$framework"
done
codesign --force \
--sign "<development-identity>" \
--entitlements Entitlements.plist \
Payload/Benchmark.app
This recipe is incomplete for bundles containing extensions, app clips, nested apps, or other signed code. Enumerate and verify the actual bundle graph; do not apply --deep as a substitute for understanding it.
Community: fastlane match, sigh, and cert can synchronize the same profile-and-identity inputs. They reduce distribution friction but do not change Apple's signing contract. altool and notarytool are not the mechanism for installing a development build on a registered iOS device.
Signing: do this / avoid this
| Do this | Avoid this |
|---|---|
| Verify the identity, profile, entitlements, and final app before install. | Treat a keychain unlock as a fix for missing account or profile authorization. |
| Use a manually managed development profile when the shell has no usable Xcode account. | Feed an Xcode-managed profile to a build explicitly configured for manual signing. |
Re-run xcodebuild -help for the installed Xcode's export keys. |
Keep using deprecated export method development; current Xcode calls it debugging. |
| Keep credentials and device identifiers out of receipts. | Dump profile contents or signing secrets into logs. |
3. Stage and Verify App-Container Data
3.1. Copy a directory, not a file into a missing parent
The basic app-container copy syntax is:
xcrun devicectl device copy to \
--device "<device-id>" \
--domain-type appDataContainer \
--domain-identifier com.example.benchmark \
--source ./run-input \
--destination Documents/run-input \
--json-output copy.json
Firsthand implementation evidence: a file-to-file copy whose destination parent did not already exist completed without producing the target file. Copying the whole local directory created the destination directory and transferred its contents. Therefore:
- Build one local staging directory for each logical payload.
- Copy that directory as a unit.
- List the remote destination after every copy.
- Verify expected names, sizes, and, when practical, content hashes in the app before loading a model.
Current command help says copy to skips files that have not been modified and supports repeated --source flags. It documents an overall --timeout, but it does not promise resumable transfer. Unverified hypothesis: interrupted multi-gigabyte transfers must be restarted. Design staging to be idempotent and independently verifiable.
3.2. List the domain directly
The raw report correctly identified a direct file-listing command:
xcrun devicectl device info files \
--device "<device-id>" \
--domain-type appDataContainer \
--domain-identifier com.example.benchmark \
--subdirectory Documents/run-input \
--recurse \
--json-output files.json
Current help confirms --subdirectory, recursive listing, filtering, sorting, and JSON output. Use this rather than treating a zero exit status from copy to as proof.
3.3. --remove-existing-content can wipe the container
Current copy to help describes --remove-existing-content as removing files from the destination directory and says it applies only to directory transfers.
Firsthand the reference implementation contradiction: --remove-existing-content true emptied the entire app data container, not only the named subtree. The observed behavior is more destructive than the help text implies. Do not use this flag in a qualifying workflow.
The safer pattern is in-app, allow-listed cleanup:
let fileManager = FileManager.default
let staleDirectory = documentsURL.appendingPathComponent("bundles/stale", isDirectory: true)
if fileManager.fileExists(atPath: staleDirectory.path) {
try fileManager.removeItem(at: staleDirectory)
}
Constrain deletion to a known child beneath a known app-owned root. Refuse absolute paths, .., symlinks that escape the root, or an empty run identifier.
3.4. Other domains
Current command help lists temporary, appDataContainer, appGroupDataContainer, and systemCrashLogs.
For an App Group, use appGroupDataContainer and the App Group identifier, not the application bundle identifier:
xcrun devicectl device info files \
--device "<device-id>" \
--domain-type appGroupDataContainer \
--domain-identifier group.com.example.benchmark \
--json-output group-files.json
Documented platform constraint, externally synthesized: the app and extensions must carry matching App Group entitlements. Treat App Group staging as a separate security boundary, not an alias for the app container.
File transfer: do this / avoid this
| Do this | Avoid this |
|---|---|
| Copy a prepared directory when the destination parent may not exist. | Assume file-to-file copy creates intermediate directories. |
| List and verify the remote subtree after every transfer. | Treat a quiet or zero-exit copy as proof of arrival. |
| Make app-side cleanup narrow and allow-listed. | Use --remove-existing-content true in a qualifying run. |
| Restart and re-verify an interrupted transfer. | Assume undocumented resume semantics. |
4. Launch and Monitor One Configuration
4.1. Argument ordering is a hard boundary
Current command help defines the grammar as <bundle-identifier-or-path> [<command-line-arguments> ...]. Firsthand implementation evidence: every token after the positional bundle identifier was forwarded to the launched app.
xcrun devicectl device process launch \
--device "<device-id>" \
--terminate-existing \
--console \
--environment-variables '{"BENCHMARK_MODE":"latency"}' \
com.example.benchmark \
--model-path Documents/run-input/encoder.mlmodelc \
--iterations 100
Here --model-path and --iterations belong to the app. Every devicectl option, including --device, --console, --environment-variables, and --terminate-existing, appears before the bundle identifier.
No -- separator is required by the documented grammar. Do not introduce one without checking the installed tool's help and the app's observed argv.
4.2. Environment variables have two supported routes
The raw report said environment variables must be passed with --environment-variables. Current command help documents two routes:
- Pass a JSON dictionary before the bundle identifier.
- Export host variables with a
DEVICECTL_CHILD_prefix.
export DEVICECTL_CHILD_BENCHMARK_MODE=latency
xcrun devicectl device process launch \
--device "<device-id>" \
com.example.benchmark
An explicit --environment-variables dictionary overrides prefixed caller variables. Do not put secrets into either route; process environments and logs are not a credential vault.
4.3. Lifecycle options
--activateis the default where supported and requests foreground activation.--terminate-existingkills an existing app instance before launch where supported. Use it when a clean process is part of the measurement contract, not as an unconditional default.--start-stoppedlaunches suspended for debugger attachment.--consoleconnects standard streams, waits for termination, and forwards catchable signals.--timeoutlimits thedevicectlcommand. It is not proof that the app persisted or completed after the host command exits.
Inspect running processes separately:
xcrun devicectl device info processes \
--device "<device-id>" \
--filter "name CONTAINS[c] 'Benchmark'" \
--json-output processes.json
Community / unverified: launching through devicectl may avoid some launch-time behavior seen with icon launches. Do not convert that anecdote into a watchdog guarantee. Measure startup, foreground state, suspension, and termination on the target OS.
Process launch: do this / avoid this
| Do this | Avoid this |
|---|---|
Put all devicectl options before the bundle identifier. |
Append --device, --console, or environment configuration after the bundle identifier. |
| Make one launch represent one immutable configuration. | Reuse an unknown running process and infer which inputs it loaded. |
| Use JSON process listings and an app-owned done marker. | Infer successful completion from "process exists" or devicectl exit alone. |
Use --terminate-existing only when clean launch semantics are intended. |
Treat termination as harmless when the prior process may own an incomplete receipt. |
5. Capture Progress and Durable Results
5.1. --console does not make Swift print durable
Firsthand implementation evidence: plain Swift print(...) produced no visible output under --console; stdout was block-buffered because it was not attached to an interactive terminal. Stderr worked, and a file written under Documents could be pulled reliably.
Use stderr for progress:
import Foundation
func emitProgress(_ message: String) {
FileHandle.standardError.write(Data((message + "\n").utf8))
}
Use a file plus an atomic done marker for the final machine-readable result:
let payload = try JSONEncoder().encode(result)
let temporaryURL = resultsURL.appendingPathComponent("\(runID).json.tmp")
let finalURL = resultsURL.appendingPathComponent("\(runID).json")
try payload.write(to: temporaryURL, options: .atomic)
try FileManager.default.moveItem(at: temporaryURL, to: finalURL)
try Data().write(to: resultsURL.appendingPathComponent("\(runID).done"))
Pull it after the done marker appears:
xcrun devicectl device copy from \
--device "<device-id>" \
--domain-type appDataContainer \
--domain-identifier com.example.benchmark \
--source "Documents/results/<run-id>.json" \
--destination "./results/<run-id>.json" \
--json-output pull-result.json
Validate the receipt schema, run ID, configuration identity, model signature, and completion status after transfer.
5.2. Unified logging
The raw report's OSLog advice remains useful: unified logging survives beyond one stdout pipe, but dynamic fields may be private unless marked public. Never mark private audio, transcripts, credentials, or device identifiers public merely to simplify debugging.
Historical device collection is supported by the host /usr/bin/log collect:
sudo /usr/bin/log collect \
--device-udid "<device-id>" \
--last 15m \
--output benchmark.logarchive
/usr/bin/log show \
--archive benchmark.logarchive \
--predicate 'process == "Benchmark"' \
--style ndjson
Correction to the raw report: on the verified host, /usr/bin/log stream --help has no --device, --device-name, or --device-udid option. The raw log stream --device-udid ... recipe is not valid there. For live output, use devicectl ... --console with stderr, an app-owned progress file, or a separately verified device-log tool. Re-check the installed OS before using a remote log stream recipe.
Console and log capture: do this / avoid this
| Do this | Avoid this |
|---|---|
| Send short progress messages to stderr. | Use Swift print as the only receipt channel under --console. |
Write final JSON to Documents and pull it after an atomic done marker. |
Parse transient console prose as the result of record. |
| Mark only non-sensitive OSLog fields public. | Expose private content to defeat <private> redaction. |
Use log collect --device-udid for historical archives. |
Publish the unsupported log stream --device-udid command. |
6. Recover Crash Evidence
systemCrashLogs is a current devicectl file-service domain:
xcrun devicectl device copy from \
--device "<device-id>" \
--domain-type systemCrashLogs \
--source . \
--destination ./crashes \
--json-output pull-crashes.json
List before pulling when a focused selection is useful:
xcrun devicectl device info files \
--device "<device-id>" \
--domain-type systemCrashLogs \
--json-output crash-files.json
Apple crash reports commonly use .ips. Correlate by process, timestamp, run window, and launch metadata rather than filename alone:
jq -r '(.procName // .process // "unknown") + " - " + (.timestamp // "unknown")' \
crash.ips
Some .ips files use a JSON metadata line followed by another JSON object; others vary by OS. Inspect the format before hard-coding head -n 1.
Community / worth checking: Core ML workloads can fail in out-of-process services such as E5 runtime or ANE compiler processes, so an app-name-only scan may miss the root cause. Pull the domain around a bounded run window and inspect all new reports. The exact service names and report schemas are OS-version dependent.
Jetsam termination is not necessarily a catchable Swift error. Treat a missing done marker, vanished process, and fresh resource-limit report as a correlated failure requiring the crash artifact, not as a timeout to retry blindly.
7. Keep Long Measurements Alive
For a foreground UIKit harness, disable automatic screen idle while a run is active:
await MainActor.run {
UIApplication.shared.isIdleTimerDisabled = true
}
Restore the prior setting after completion.
Community / platform hypothesis: a standard app can still be suspended after it is backgrounded or the device locks. A devicectl launch does not grant indefinite background execution. Keep the measurement app foreground, keep the device in a controlled powered state, and make suspension observable through timestamps and done markers.
Do not claim an audio, location, or other background mode solely to keep a benchmark alive. Use only a mode that the app genuinely performs and Apple's policy permits. If foreground operation is impossible, design the run into resumable bounded units rather than hiding it behind an unrelated entitlement.
Thermal state, battery state, charging, screen state, and other active Core ML clients can change latency. Bind those conditions into the experiment protocol when they matter, but keep device identifiers out of public receipts.
8. Memory, JIT, AOT, and Jetsam
The raw report preserves a useful failure model but overstates it as universal:
- Community reports: some iPhones exhibit a per-process memory ceiling near 6 GB even when total physical memory is larger.
- Community reports: on-device compilation of large Core ML or MPSGraph packages can create transient resident-memory spikes and trigger jetsam or compiler-service failure.
- Community hypothesis: ahead-of-time compiled
.mlmodelcassets can reduce compilation work and permit more file-backed paging than loading an uncompiled package. - Community reports: the increased-memory-limit entitlement may provide limited or device-dependent relief, with different behavior on iPad.
None of those numbers is portable across devices, OS builds, entitlements, model formats, or Core ML revisions. Measure peak physical footprint in the app, record cold-load and warm-run separately, and pair every unexplained termination with systemCrashLogs.
Prefer a compiled model for device deployment:
xcrun coremlcompiler compile \
"/path/to/Model.mlpackage" \
.build/compiled
Unverified mechanism: memory mapping can move some model storage from dirty resident memory to file-backed pages, but .mlmodelc is not a promise of zero runtime compilation or safe peak memory. The proof is a target-device cold-load receipt plus crash scan.
9. Inspect MLComputePlan Correctly
9.1. Current documented Swift surface
Context7 verification against Apple Core ML documentation gives these signatures:
static func load(
contentsOf url: URL,
configuration: MLModelConfiguration
) async throws -> MLComputePlan
func deviceUsage(
for operation: MLModelStructure.Program.Operation
) -> MLComputePlan.DeviceUsage?
func estimatedCost(
of operation: MLModelStructure.Program.Operation
) -> MLComputePlan.Cost?
deviceUsage(for:) returns nil when usage cannot be determined. Swift DeviceUsage exposes preferred and supported compute devices. estimatedCost(of:) exists for ML Program operations and returns an optional cost. Cost.weight is documented as an estimated workload fraction from 0.0 to 1.0 over the total model evaluation.
9.2. Minimal on-device walk
Pass the compiled .mlmodelc URL and the same configuration used for the model run:
import CoreML
import Foundation
@available(iOS 17.4, *)
func writeComputePlan(
compiledModelURL: URL,
configuration: MLModelConfiguration
) async throws {
let plan = try await MLComputePlan.load(
contentsOf: compiledModelURL,
configuration: configuration
)
guard case let .program(program) = plan.modelStructure,
let main = program.functions["main"] else {
throw BenchmarkError.expectedMLProgram
}
for operation in main.block.operations {
let usage = plan.deviceUsage(for: operation)
let cost = plan.estimatedCost(of: operation)
let line = [
operation.operatorName,
String(describing: usage?.preferred),
String(describing: cost?.weight),
].joined(separator: "\t")
FileHandle.standardError.write(Data((line + "\n").utf8))
}
}
The BenchmarkError case is app-owned and must be defined by the harness.
9.3. What the values prove
usage?.preferredis anticipated preferred placement for that operation under the supplied configuration. It is not a trace proving where a completed prediction executed.usage?.supportedcan describe available devices where exposed, but support is not selection.cost?.weightis an estimate, not milliseconds. Aggregate it within one plan; do not treat it as absolute latency.- Firsthand implementation evidence:
constoperations returnednilfor both usage and cost in the tested compiled program. Apple documents the optional return and undetermined-usage meaning, not a universal "all const operations are nil" rule. Keep the unwrap for every operation. - The plan is model-, shape-, configuration-, device-, and OS-specific. A simulator or Mac plan does not certify a physical iPhone.
The raw report called on-device Swift output "irrefutable ground truth." That is too strong. It is more target-specific than an off-device estimate, but it remains a compute plan. Pair it with latency, runtime telemetry, or a trace before claiming actual ANE, GPU, or CPU execution. See the Neural Engine residency guide for that distinction.
10. What Not to Do
| Anti-pattern | Why it fails | Do this instead |
|---|---|---|
| Stage a bare file into a missing remote parent. | Firsthand runs silently produced no target file. | Copy one directory and list it afterward. |
Add --remove-existing-content true for cleanliness. |
Firsthand runs emptied the whole app container. | Delete an allow-listed subtree inside the app. |
Put a devicectl flag after the bundle identifier. |
It becomes app argv. |
Put every tool option before the positional bundle identifier. |
Depend on Swift print under --console. |
Buffered stdout can remain silent. | Use stderr for progress and a result file for record. |
Use log stream --device-udid. |
That option is absent on the verified host. | Use historical log collect or a separately verified live route. |
| Scan only app-named crash files. | Core ML or ANE services may fail independently. | Pull and time-filter the complete systemCrashLogs domain. |
| Read preferred compute-plan device as executed device. | The API reports anticipated use. | Pair the plan with runtime evidence. |
| Hard-code a 6 GB jetsam ceiling. | Memory policy varies by target and workload. | Measure peak footprint and collect termination evidence. |
| Assume automatic signing works without an Xcode account. | It failed firsthand with "No Accounts." | Use a matching manually managed development profile. |
| Re-sign only the outer app. | Nested signed code can retain a mismatched identity or entitlement set. | Enumerate, sign inside-out, and verify the final bundle. |
11. Debugging Playbook
Copy reports success but the file is absent
- List the intended parent with
device info files. - If the parent is absent, copy the containing local directory instead of the file.
- Pull or app-hash the staged payload before loading it.
- Preserve the copy JSON and file-list JSON as operational evidence.
Previous app state disappeared
- Search the driver command for
--remove-existing-content. - Treat the entire container as potentially wiped.
- Restore only known public inputs.
- Remove the flag permanently and move cleanup into bounded app logic.
The app receives --device or other unexpected arguments
- Capture the app's
CommandLine.argumentsto stderr or a result file. - Move every
devicectloption before the bundle identifier. - Keep only app arguments after the bundle identifier.
--console is blank
- Confirm the process exists with
device info processes. - Emit one flushed stderr line.
- Poll for an app-owned progress or done file.
- Pull a historical log archive if OSLog was enabled.
Signing reports "No Accounts"
- Decide whether the runner is supposed to use an interactive Xcode account. Do not assume.
- If no account is available, select a manually managed development profile that authorizes the target.
- Verify that the development identity and private key are visible to the shell.
- Build with manual signing and inspect the final entitlements before install.
Signing says the profile is Xcode-managed but manual signing is required
- Stop mixing an Xcode-managed profile with manual signing.
- Create or obtain a non-Xcode-managed development profile for the app and registered target.
- Install it without logging its private payload.
- Rebuild with
PROVISIONING_PROFILE_SPECIFIERnaming that profile.
The process vanishes during model load
- Do not retry immediately.
- Pull
systemCrashLogsfor the bounded launch window. - Check app resource-limit reports and Core ML, E5, or ANE service reports.
- Separate cold compilation from warm inference.
- Try an AOT-compiled
.mlmodelcas a diagnostic, then remeasure peak footprint.
Compute plan reports no device or no cost
- Confirm the model is an ML Program and the walked operation belongs to the loaded plan's structure.
- Keep optional handling;
nilmeans the value could not be determined. - Record operation name, model signature, compute units, OS, and device class without a unique identifier.
- Do not replace missing plan data with guessed placement.
12. Ingest Decision Log
Corrected against current Core ML documentation
| Raw-report framing | Correction | Evidence |
|---|---|---|
On-device MLComputePlan is execution ground truth. |
deviceUsage(for:) reports anticipated usage and returns optional DeviceUsage; runtime execution needs separate evidence. |
Apple Core ML via Context7 |
const operations return nil for usage and cost as an API rule. |
The APIs are optional; deviceUsage is nil when usage cannot be determined. Const-nil is retained as a firsthand observation, not a universal contract. |
Apple Core ML via Context7; the reference implementation harness |
| Compute-plan loading was shown without an exact contract. | The exact API is static func load(contentsOf:configuration:) async throws -> MLComputePlan. |
Apple Core ML via Context7 |
| Cost was treated as available on every operation. | estimatedCost(of:) exists but returns MLComputePlan.Cost?; weight is a 0.0...1.0 estimated workload fraction. |
Apple Core ML via Context7 |
Corrected against current command help
| Raw-report framing | Correction | Evidence |
|---|---|---|
Environment variables must use --environment-variables. |
Current process launch also accepts caller variables prefixed with DEVICECTL_CHILD_; explicit JSON overrides them. |
Xcode 26.6 devicectl 518.33 help |
log stream --device-udid streams a physical device. |
The verified /usr/bin/log stream has no device-selector option; /usr/bin/log collect does. |
macOS 26.6 command help |
Export method development is current. |
Xcode 26.6 deprecates it in favor of debugging. |
xcodebuild -help |
--terminate-existing should always be used in CI. |
It is appropriate only when terminating prior work is part of the run contract. | Current option semantics |
Firsthand the reference implementation annotations retained
- Missing destination parents can make file-to-file
copy tosilently produce nothing; directory copy works. --remove-existing-content trueemptied the entire app data container, contradicting the narrower current help wording.- Tokens after the process-launch bundle identifier are app arguments.
- Swift
printwas silent under--console; stderr and aDocumentsresult file worked. - With no Xcode account signed in, a manually managed development profile authorizing the target worked and an Xcode-managed profile did not.
systemCrashLogsis available for crash retrieval.
Preserved as community or unverified
- Multi-gigabyte transfers lack resume and must restart after interruption.
devicectllaunch changes launch-watchdog behavior.- Foreground activation plus idle-timer suppression is sufficient for a long unattended run.
- A roughly 6 GB per-process ceiling applies to modern iPhones.
- JIT compilation drives transient jetsam failures, while AOT compilation and file-backed mapping reduce the peak.
- Increased-memory-limit entitlements add little headroom on iPhone and more on iPad.
- Core ML, E5 runtime, and ANE compiler services can crash independently of the app.
- Profile filename conventions under
~/Library/MobileDevice/Provisioning Profiles/. - Inside-out
codesignre-signing and fastlane-based credential synchronization.
Each remains concrete enough to test. None is promoted to a target-device fact without a bounded receipt.
Removed with evidence
- The executable
log stream --device-udid ...recipe was removed because the verified host rejects that option. The underlying need for live device logging is retained with supported and explicitly unverified alternatives. - No other substantive mechanism, workaround, deployment route, or failure mode was removed.
Claim-Coverage Audit
| Raw report section | Substantive items | Disposition |
|---|---|---|
| Executive summary | Container wipe, argv boundary, silent stdout, crash domain, long execution, memory, compute plan, signing | All kept; absolutes corrected or relabeled |
| App-container transfer | Copy to/from, missing parents, file listing, no resume, App Groups, destructive cleanup | All kept; first three verified by command help or firsthand evidence |
| Process launch | Argument order, environment, start-stopped, terminate-existing, process listing | All kept; environment route and unconditional termination corrected |
| Console and unified logging | stderr, result files, OSLog privacy, stream and collect | Kept; unsupported remote log stream command removed with evidence |
| Crash retrieval | systemCrashLogs, .ips, daemon failures, correlation |
All kept; schema and daemon names labeled version-dependent |
| Unattended execution | Foreground activation, idle timer, suspension, background modes | All kept as platform or community guidance |
| Memory and jetsam | Approximate ceiling, resource termination, JIT spike, AOT and paging, entitlement | All kept and relabeled unverified |
MLComputePlan |
Load, device usage, cost, const nil, on-device versus off-device | All kept; API optionality and proof strength corrected |
| Signing | No Accounts, manual profile, archive/export, profile installation, re-signing, fastlane, unrelated tools | All kept; local requirement separated from universal claims |
| Failure matrix | Silent copy, wipe, argv, signing, jetsam, blank console | All retained in the debugging playbook |
Works Cited
- Apple Core ML:
MLComputePlan.load(contentsOf:configuration:)— exact asynchronous Swift load signature. - Apple Core ML:
deviceUsage(for:)— optional anticipated device usage for an ML Program operation. - Apple Core ML:
estimatedCost(of:)— optional operation cost. - Apple Core ML:
Cost.weight— estimated workload fraction from zero to one. - Current Xcode 26.6 command help:
xcrun devicectl help device copy to,copy from,info files,info processes,process launch, andinstall app. - Current macOS command help:
/usr/bin/log stream --helpand/usr/bin/log collect --help. - Current Xcode export help:
xcodebuild -help.