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
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.
Copy file name to clipboardExpand all lines: .agents/docs/2026-09-11-platform-targets-design-review.md
+98-25Lines changed: 98 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -313,7 +313,7 @@ package -- no new mechanism, again.
313
313
| 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 |
314
314
| 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 |
315
315
| 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|
317
317
318
318
## 7. User-facing experience, which is the test of all of the above
319
319
@@ -605,30 +605,103 @@ And the ordering is a real dependency rather than a convention: the iOS device
605
605
runner has nothing to install until `dist-ipa` has produced a signed file, so
606
606
R9 precedes R12.
607
607
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.
632
705
633
706
### 10.8 Darling is a candidate for the macOS rows, and is recorded as unmeasured
0 commit comments