Skip to content

Commit d62d46e

Browse files
committed
docs(targets): the runner program owns the image path, because a path is a machine fact and a declaration is not
Three homes for an iOS image path were considered, and two are wrong for reasons worth recording because each looked right first. A LOCATOR PACKAGE has nothing to probe. Locators work where the thing has a conventional location -- vswhere, /Applications/Xcode.app -- and an image a user legally owns is wherever they put it. Its version axis would be meaningless too: a locator for 18.0 cannot verify that what it found is 18.0. THE PATH IN build.mcpp FAILS ON THIS REPOSITORY'S OWN ANALYSIS. A build program is per-package, committed, and its declarations are persisted in the cache record; a path is per-MACHINE, not committable, and must not be a build input or identical sources produce different build directories. That is exactly the precedence #564 established for `[build] default_jobs` -- invocation > project > machine -- and an image path sits on the machine level as a job count does. And a sharper failure: a variable read through `env_or` is NOT in the build program's contract environment, so it is not in its re-run key. Change the path and the program does not re-run, while the cache record persists the old runner (tag "runner" is non-empty). A path that was changed and appears not to have been is the defect class recorded most often here, and this would introduce it deliberately. THE DESIGN: the runner PROGRAM owns the path, resolved at run time. The engine names a runner and does not know what an image is; the index ships the emulator and no image; the plugin declares WHICH runner and produces the artefact; the program reads its own configuration each invocation. The path enters mcpp at no point -- not the index, the build program, the cache record or the fingerprint. That is the boundary cargo states for itself and which section 5.2 already quotes, applied one level further: "that responsibility falls to the runner program itself." It dissolves the staleness rather than mitigating it, because a run-time resolution has no record to go stale. The distinction the section arrives at is one sentence: declaring a NAME is cache-safe, baking a PATH is not.
1 parent 59d37e7 commit d62d46e

1 file changed

Lines changed: 98 additions & 25 deletions

File tree

‎.agents/docs/2026-09-11-platform-targets-design-review.md‎

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ package -- no new mechanism, again.
313313
| R10 | `--format dmg` and `--format pkg` | recorded as gaps with a known shape, not attempted | each needs a *creator* as well as a signer (`libdmg-hfsplus`; `xar`), both open source and neither measured here |
314314
| R11 | the macOS rows' runner | Darling recorded as an unmeasured candidate | GPL-3.0, active, and it REIMPLEMENTS Darwin's libraries rather than redistributing them, so unlike the iOS image it carries no licence blocker. A row does not move on a plausible mechanism, so this is a candidate and not a plan |
315315
| R12 | real-device run for both platforms | `xim:android-platform-tools` (have) and a new `xim:pymobiledevice3`, each named by a `runner` program | neither needs Apple or Google software. It supersedes the simulator route rather than complementing it: a device brings its own OS, so the only thing crossing the boundary is a signature the developer already owns |
316-
| R13 | the iOS image | a LOCATOR package, never a re-host, gated on R4 | an image in a public index is redistribution of Apple's OS whatever it is labelled. The locator is the tier `iphoneos-sdk.lua` already documents, and with R12 in place no image is on the critical path at all |
316+
| R13 | the iOS image | **the RUNNER PROGRAM owns the path, resolved at run time.** The plugin declares a runner by NAME; the index ships the emulator and no image; the user configures the program | three homes were considered. A locator has nothing to probe. `build.mcpp` is per-package and committed, while a path is per-MACHINE -- the same analysis #564's `default_jobs` needed -- and a path read through `env_or` is not in the program's re-run key, so changing it would appear not to change anything while the cache record persists the old one. Declaring a NAME is cache-safe; baking a PATH is not. Run-time resolution has no record to go stale |
317317

318318
## 7. User-facing experience, which is the test of all of the above
319319

@@ -605,30 +605,103 @@ And the ordering is a real dependency rather than a convention: the iOS device
605605
runner has nothing to install until `dist-ipa` has produced a signed file, so
606606
R9 precedes R12.
607607

608-
### 10.7 The iOS image: a locator, not a re-host
609-
610-
Adding an iOS kernel and root filesystem to a public index would be
611-
redistributing Apple's operating system, and a "temporary, test-only, disabled
612-
later" label does not change that -- anyone resolving the index would install
613-
it. This differs from the Android decision earlier in this document in a way
614-
worth stating precisely: there, Apache-2.0 licence files were verified INSIDE
615-
the archives and clause 3.5 genuinely applies; here there is no
616-
open-source component to invoke.
617-
618-
What serves the same purpose legitimately is the third tier
619-
`pkgs/i/iphoneos-sdk.lua` already documents, and R4's `@system`
620-
generalisation is the engine half of it:
621-
622-
a LOCATOR package records where an image the user already owns lives.
623-
Nothing is re-hosted; the index carries a path and a probe, not bytes.
624-
625-
That is the `msvc@system` shape, and it is why R4 matters beyond iOS: the
626-
engine currently has no spelling for "this row's system is host-located",
627-
so the locator tier is unreachable even though the recipe describes it.
628-
629-
And it is worth noting what the locator would be FOR. With R12 in place, a
630-
simulator or an emulated image is not on the critical path at all -- a real
631-
device is the supported route, and it needs no image from anyone.
608+
### 10.7 The iOS image: the ecosystem supplies the PROGRAM, the user supplies the BYTES
609+
610+
The principle is not in question: an image in a public index is redistribution
611+
of Apple's operating system whatever it is labelled, and a "temporary, disabled
612+
later" flag does not change it -- anyone resolving the index installs it. This
613+
differs from the Android decision earlier in this document in a way worth
614+
stating precisely: there, Apache-2.0 licence files were verified INSIDE the
615+
archives and clause 3.5 genuinely applies; here there is no open-source
616+
component to invoke.
617+
618+
What took analysis is WHERE the path lives. Three candidates were considered
619+
and two are wrong for reasons worth recording, because each looked right first.
620+
621+
#### Rejected: a locator package
622+
623+
A locator works when the thing has a CONVENTIONAL location to probe --
624+
`vswhere` for Visual Studio, `/Applications/Xcode.app` for Xcode. An image a
625+
user legally owns is wherever they put it, so a locator has nothing to probe
626+
and would be a package whose entire content is a question. Its version axis
627+
would be meaningless too: a locator for `iphoneos-image@18.0` cannot verify
628+
that what it found is 18.0.
629+
630+
#### Rejected: the path in `build.mcpp`, and the reason is this repository's own
631+
632+
A build program is per-package, committed to a repository, and its declarations
633+
are persisted in the build cache record. An image path is none of those things:
634+
635+
it is per-MACHINE two developers keep it in different places
636+
it is not committable an absolute path in someone's home directory
637+
it must not be a build input identical sources must not produce different
638+
build directories because a path differs
639+
640+
That is precisely the analysis `[build] default_jobs` needed (#564): the
641+
precedence is invocation > project > **machine**, and an image path sits on the
642+
machine level exactly as a job count does. Putting a machine fact in a
643+
per-package file is the shape that key was fixed for.
644+
645+
And there is a sharper failure. If a build program reads
646+
`MCPP_IOS_IMAGE_ROOT` through `env_or`, that variable is **not** part of
647+
mcpp's contract environment, so it is not in the program's re-run key. Change
648+
the path and the program does not re-run; the stale runner replays from the
649+
cache record, which persists it (tag `"runner"`). The result is a path that was
650+
changed and appears not to have been -- the defect class this repository has
651+
recorded most often, and here it would be introduced deliberately.
652+
653+
#### The design: the runner PROGRAM owns the path, and nothing above it knows
654+
655+
engine names a runner. Does not know what an image is.
656+
index ships the emulator (QEMU is packageable) and NO image.
657+
plugin declares WHICH runner, and produces the artefact to run.
658+
runner a program in a xim package. Owns the path, at RUN time.
659+
user keeps the bytes, and tells the runner program where they are.
660+
661+
The path enters mcpp at no point: not the index, not the build program, not the
662+
cache record, not the fingerprint. What is published is a program that takes a
663+
path, and the one thing crossing the boundary is the user's own configuration
664+
of that program.
665+
666+
This is the boundary cargo states for itself, quoted earlier in §5.2: managing
667+
devices and simulators is out of scope, and "that responsibility falls to the
668+
runner program itself." A runner program that owns its own configuration is the
669+
same sentence applied one level further.
670+
671+
It also dissolves the staleness problem rather than mitigating it. A run-time
672+
resolution cannot be stale, because there is no record to go stale -- the
673+
program reads its configuration each time it is invoked, which is what a
674+
machine fact wants.
675+
676+
#### What each layer actually writes
677+
678+
The plugin side declares the runner by name, so it is available and not
679+
imposed:
680+
681+
// a dist/run member, or the project's own build.mcpp
682+
mcpp::runner("device", "mcpp-ios-device-run"); // needs no image at all
683+
mcpp::runner("qemu", "mcpp-ios-qemu-run"); // reads its own config
684+
685+
reached as `mcpp run --runner device` or `--runner qemu`. Two properties of the
686+
existing machinery make this work unmodified: the `runner` directive's cache
687+
tag is non-empty so a declaration survives a cache hit, and its
688+
`Scope::RunGlobal` is correct because a runner is a property of the invocation
689+
rather than of one package in the graph. **Declaring a NAME is cache-safe;
690+
baking a PATH is not** -- which is the whole distinction this section arrived
691+
at.
692+
693+
#### This is a pattern, and naming it is worth more than the iOS instance
694+
695+
The same contract serves every "you have it, we cannot ship it" case: a vendor
696+
BSP under NDA, a licensed board-support blob, proprietary firmware, a paid SDK.
697+
In each, the ecosystem packages the TOOL that consumes the bytes and never the
698+
bytes, and the tool owns its own configuration.
699+
700+
Stating it as a pattern matters because the alternative -- deciding case by
701+
case -- is how a "temporary" entry becomes permanent. And it composes with R12
702+
in the direction that counts: **with a real device supported, no image is on
703+
the critical path at all.** The image route serves a developer who has one and
704+
prefers it; the row does not depend on it.
632705

633706
### 10.8 Darling is a candidate for the macOS rows, and is recorded as unmeasured
634707

0 commit comments

Comments
 (0)