@@ -1001,3 +1001,149 @@ on the one above it being released rather than merely merged.
10011001
10021002The engine tasks are the only ones on the critical path. P1 and X1 do not wait
10031003on anything.
1004+
1005+ ## 11. The plan read from nine angles
1006+
1007+ Section 9 reviewed the proposal on its own terms. This section reads the
1008+ * implemented* result from the angles a reviewer would apply independently of it,
1009+ because each angle catches a different class of mistake and several of them
1010+ caught one.
1011+
1012+ ### 11.1 Architecture
1013+
1014+ The load-bearing claim is that ** the engine holds the dispatch and no format** .
1015+ It survives one test the proposal did not anticipate: a format that consumes
1016+ nothing. ` dist-wix ` packages one named program and never reads the staged tree,
1017+ which is what §6's own guidance recommends — and the first implementation of the
1018+ dispatch refused exactly that member, because it identified the distributable by
1019+ "which action named ` ${mcpp.stage_dir} ` ". The criterion was a property of the
1020+ * mechanism* rather than of the * request* . It is now "which artifact actions the
1021+ request introduced", which needs nothing of the member.
1022+
1023+ The same mistake occurred one layer down and was found by a real macOS runner:
1024+ staging ran before the dispatch and its failure was fatal, so every dispatched
1025+ format was unreachable on a target whose built-in bundling is refused. Staging
1026+ is a service to the provider, not a precondition.
1027+
1028+ Both are the same error in different clothes: ** the engine deciding something on
1029+ the provider's behalf.** That is the failure this architecture is most exposed
1030+ to, because the whole point of it is that the provider decides.
1031+
1032+ ### 11.2 Stability
1033+
1034+ Three axes now ride ` build.ninja ` 's header line — shape, schedule, device
1035+ variant — and ` dist= ` is the fourth. Each was added after the same defect: a
1036+ graph written for one purpose replayed for another, in a directory the two
1037+ share. The format deliberately does not enter the fingerprint, because it would
1038+ cost a full recompile to package an already-built tree; the header line is the
1039+ cheaper half of that pair and is the half the fast paths ask.
1040+
1041+ The measurement that matters is the one that says the criterion is worth
1042+ having: on 2026-09-11 a plain build after a pack pass regenerates the graph
1043+ * even with the field ignored* , so an end-to-end assertion would pass whether or
1044+ not the field works. The unit test is where the invariant is held.
1045+
1046+ ### 11.3 Elegance
1047+
1048+ Two additions were withdrawn as duplicates of something that ships.
1049+ ` --stage-only ` is ` --format dir ` . And ` rule_module ` on a ` dist-* ` feature was
1050+ refused by the engine, correctly: that key means "the module that reaches a
1051+ rule" and implies ` device_extensions ` , which a member compiling nothing cannot
1052+ have. The ` dist-* ` members take the ` tools-* ` shape instead, and the consumer
1053+ writes one line more than a rule needs — which says something true.
1054+
1055+ ### 11.4 User experience
1056+
1057+ ` --format ` gained values rather than a second flag, because ` tar ` , ` dir ` , ` msi `
1058+ and ` appimage ` answer one question. An unknown value names what * is* available
1059+ rather than a fixed list, and the refusal arrives before anything is compiled.
1060+
1061+ The failure mode this category is most exposed to is a step that succeeds while
1062+ carrying nothing — §2's measured 52 KB installer. Every member therefore
1063+ asserts a floor on the success path through ` mcpp::warning ` , because stderr on
1064+ a successful build is discarded. The first such floor was a size bound and was
1065+ wrong on its first real fixture: a stripped hello-world stages at 14999 bytes,
1066+ under a 16 KB bound, so a correct AppImage was reported as empty. A size is a
1067+ proxy for a question that can be asked directly.
1068+
1069+ ### 11.5 Compatibility
1070+
1071+ The engine's rule is unchanged: no per-package floor exists, and the index-level
1072+ ` min_mcpp ` does not move for a package, because raising it makes the whole index
1073+ unreadable to clients stopped below it. What an older client gets is legible at
1074+ the point of use, and for these members it is the best case of that rule —
1075+ ` mcpp::provides_pack_format ` does not exist in an older engine's bundled module,
1076+ so a consumer fails at the ` build.mcpp ` ** compile** , naming the missing
1077+ function, rather than at a link or in an artifact.
1078+
1079+ ### 11.6 Cross-platform
1080+
1081+ Three members, three platforms, and the honest asymmetry is that only one of
1082+ them could be measured where it was written. ` plan_for() ` returning
1083+ ` applies == false ` on the wrong OS says the gate works and says nothing about
1084+ whether the tool accepts what the member renders. That gap was closed by adding
1085+ CI steps that actually run ` wix build ` and assemble a real ` .app ` — and the
1086+ first thing they did was fail, twice, for unrelated reasons: WiX 7 refuses to
1087+ run without an out-of-band licence acceptance (` WIX7015 ` ), and the Mach-O
1088+ staging refusal above. Both are findings the plan-level assertions could not
1089+ have produced.
1090+
1091+ ### 11.7 Consistency
1092+
1093+ ` rules-* ` , ` tools-* ` and ` dist-* ` is one taxonomy with one rule: the prefix
1094+ says which of three questions a member answers. The engine's own three-value
1095+ ` ObjectFormat ` is the same discipline applied to a fact rather than to a
1096+ package — the binary format was re-derived at roughly 35 sites, which is
1097+ affordable at two values and becomes an addition at every site at three, where
1098+ a missed site silently answers ELF.
1099+
1100+ Two sites answered the object format by searching for ` "apple" ` in a string
1101+ that never contains it, so an explicit ` --target aarch64-macos ` — a * verified*
1102+ row — linked and recorded as ELF while a native build on the same machine said
1103+ ` macho ` . One function, two paths, only the exercised one right.
1104+
1105+ ### 11.8 Seamless upgrade
1106+
1107+ Every new field is absent-tolerant in the direction that matters. An older
1108+ graph's missing ` dist= ` reads as a miss and never as ` none ` . A build program on
1109+ an older engine gets empty strings from the new accessors, which a member reads
1110+ as "fall back to what you did before". The ` pack-format ` directive carries a
1111+ non-empty cache tag, so a declaration survives a cache hit — the pass that
1112+ reads it is ` mcpp pack ` , which is never a project's first build, and an
1113+ unpersisted declaration would be absent exactly when a user names a format.
1114+
1115+ ` kCacheEpoch ` is deliberately not bumped: an entry written before the row
1116+ carries no such line and the program that wrote it could not emit one, so
1117+ replaying it yields what that program said.
1118+
1119+ ### 11.9 Test coverage
1120+
1121+ The count is not the measure; what each test excludes is. Two are worth naming.
1122+
1123+ ` 638 ` holds nine properties, each paired with the wrong answer it excludes, and
1124+ two of them were verified load-bearing by removing the guard and watching the
1125+ test fail — including the one that distinguishes "the request introduced this
1126+ action" from "any artifact action".
1127+
1128+ And one CI assertion was itself the defect: it grepped for `struct
1129+ embedded_file` , the *default* ` row_type`, while the fixture sets
1130+ ` row_type = "shader_entry" ` precisely because that option exists. The code was
1131+ correct and the assertion was wrong, which is what a check tied to a spelling
1132+ the fixture chooses will eventually always be. It now reads the struct's name
1133+ out of the file and asserts the table's element type * is* that struct, for
1134+ every generated header rather than whichever ` find ` listed first.
1135+
1136+ ### 11.10 The dependency order, and why it is not the demand order
1137+
1138+ mcpp engine ──────────────► released, because plugins CI pins a release
1139+ │
1140+ ├─► xim payloads ─────► independent of the engine; merged first
1141+ │
1142+ └─► mcpp-plugins ─────► needs the release, so it cannot precede it
1143+ │
1144+ └─► mcpp-index ► needs the plugins tag's sha256
1145+
1146+ Four repositories, one pull request each. The engine is the only thing on the
1147+ critical path, and every measurement that changed the plan came from the layer
1148+ * above* it — which is the argument for doing the payloads early even though the
1149+ rows land last.
0 commit comments