@@ -107,23 +107,24 @@ both keys (2026.9.2.1) and it survives the merge unmodified.
107107
108108## 4. What mcpp writes into ` .xlings.json `
109109
110- This is the decision the rest depends on, and it is xlings' to make.
111-
112- ** Option A: mcpp writes only ` workspace ` .** The materialisation stops emitting
113- a ` deps ` array; every entry lands in the ` workspace ` object, and xlings
114- provisions from it. This is the shape the proposal is written to, and it holds
115- only if xlings provisions from ` workspace ` . If it does not, a project that
116- migrated would build on a machine where the packages happen to be installed and
117- fail on a clean one, which is the failure mode with the longest detection
118- delay.
119-
120- ** Option B: mcpp derives a ` deps ` array from ` workspace ` when writing.** mcpp
121- keeps one table in ` mcpp.toml ` and emits both fields. This works whatever
122- xlings does, and it is a translation layer of exactly the kind §2.13 refuses.
123- It is acceptable only as a transitional step with a stated end.
124-
125- The recommendation is A, conditional on Q1 and Q2. B is the fallback and must
126- be labelled transitional in the code rather than left to look permanent.
110+ ** Decided (2026-09-03): mcpp writes ` workspace ` , and nothing else changes.**
111+ The correspondence between ` [xlings] ` and ` .xlings.json ` is one to one in name
112+ and in meaning, so ` [xlings.workspace] ` materialises as the file's ` workspace `
113+ object and the merged semantics is xlings' own. The ` deps ` array stops being
114+ emitted when ` deps ` is retired.
115+
116+ The alternative — deriving a ` deps ` array from ` workspace ` when writing — is
117+ rejected. It is a translation layer, which §2.13 refuses, and it would put the
118+ same statement in the file twice, which is the drift shape section 1 exists to
119+ remove.
120+
121+ One consequence for section 3's W2. mcpp does not only pass the author's
122+ entries through: it appends the target's C library to the same channel
123+ (` prepare.cppm:3186-3194 ` ), and the value comes from the target row as
124+ ` xim:picolibc-riscv@1.8.12 ` — a namespaced reference with a version. So a
125+ workspace key that cannot carry a namespace would make mcpp unable to express
126+ its own injected entry, not merely inconvenience an author. W2 is a
127+ prerequisite rather than a preference.
127128
128129## 5. What provisioning means after the merge
129130
@@ -292,3 +293,108 @@ this repository has read that measurement wrongly twice.
292293 when a package ships several programs.
2932945. **Phase 3's floor.** Which mcpp version the migrating packages declare, and
294295 whether the index sweep is a release gate or a one-off.
296+
297+ ## 12. The section as a whole
298+
299+ The proposal changes one field of a section whose other fields are unaffected.
300+ This is what `[xlings]` is at `4d99864`, so that a review of the change can see
301+ what it is being made against.
302+
303+ ### 12.1 Field correspondence
304+
305+ | `mcpp.toml` | `.xlings.json` | Shape | Readers in mcpp |
306+ |---|---|---|---|
307+ | `[xlings] deps` | `deps` | array of package references | five (§1); retired by this proposal |
308+ | `[xlings.workspace]` | `workspace` | object, name to version | one, the materialisation; five after this proposal |
309+ | `[xlings] subos` | `subos` | string | the materialisation, and `select_runtime` |
310+ | `[xlings.envs]` | `envs` | object, name to value | one, the materialisation |
311+ | `[indices]` (not under `[xlings]`) | `index_repos` | array of repo objects | `ensure_project_index_dir` |
312+ | — | `lang`, `mirror` | strings | written by mcpp unconditionally |
313+
314+ Names and meanings correspond one to one, and mcpp adds no key of its own. The
315+ file is written by `seed_xlings_json` (`src/xlings/xlings.cppm`), each field
316+ emitted only when non-empty.
317+
318+ ### 12.2 Three places mcpp is not a pure mirror
319+
320+ Stated because a "1:1, no translation layer" claim is checkable, and these are
321+ the exceptions to it.
322+
323+ 1. **mcpp appends an entry the manifest did not write.** The target's C library
324+ is added to the package channel, deduplicated, when the target row names one
325+ (`prepare.cppm:3186-3194`). It rides that channel rather than having one of
326+ its own so that one materialisation can be wrong instead of two. It is also
327+ the reason §3's W2 is a prerequisite.
328+ 2. **What is written is already resolved for this host.** A per-platform value
329+ is collapsed at manifest load (`resolve_host_value`), so the file is a
330+ materialisation for this machine rather than a copy of the declaration. §7
331+ is the consequence.
332+ 3. **`lang` and `mirror` are mcpp's, not the manifest's.** They come from
333+ mcpp's own configuration and are always present in the file.
334+
335+ ### 12.3 Ownership: who declares the environment
336+
337+ One rule, in `mcpp.xlings.runtime_selection`, whose header states what it
338+ deliberately does not read: the process environment, xlings' active or current
339+ state, the compiler path, and dependency manifests. Allowing any of them would
340+ make one `mcpp.toml` mean different ABIs in different shells.
341+
342+ - In a workspace build the **workspace root** owns the declaration, even after
343+ the package manifest switches to a selected member. An independently built
344+ member is its own owner.
345+ - A dependency's `[xlings]` is never consulted and never propagated. A
346+ library's declaration applies when it is a root, not when its sources are
347+ consumed by another root.
348+ - The file is written under the owner's root
349+ (`<ownerRoot>/.mcpp/.xlings.json`). When the owner is not the directory mcpp
350+ writes into, two files are written: indices to the work root, the environment
351+ to the owner root.
352+ - Nothing is written at all unless the project declares indices, or declares
353+ `[xlings]`, or the target row names a C library
354+ (`materializeRootRuntime`, `prepare.cppm:3179-3181`).
355+
356+ ### 12.4 `subos`: presence is semantic, and mcpp only reads
357+
358+ - **Absent** selects mcpp's initialised, release-verified `McppDefault`.
359+ **`subos = "default"`** is an explicit `NamedSubos("default")`. A string alone
360+ cannot distinguish absence from an empty value, which is why the manifest
361+ carries `subosDeclared` beside it.
362+ - The name is validated as a portable identifier (letters, digits, `.`, `_`,
363+ `-`); anything else is a manifest error naming the value.
364+ - There is **no CLI or environment override**, and no implicit following of
365+ xlings' active or current SubOS.
366+ - A named SubOS that does not exist is a **hard error**, never a fallback:
367+ falling back would substitute a different environment for the one the
368+ manifest named. Creating and populating one is xlings' layer
369+ (`xlings subos new`); mcpp reads an environment and never creates one.
370+ - An environment that exists but carries no `subos_info` **degrades**: the
371+ runtime binding reports inconclusive, a note is printed, and the build
372+ continues.
373+ - On Linux the selection also fixes the loader and C library contract, so two
374+ SubOS names produce separately fingerprinted objects.
375+ - Only a **declared** SubOS puts its `bin/` at the front of `build.mcpp`'s
376+ `PATH` (`projectSubosBin` is non-empty only for `Mode::NamedSubos`,
377+ `prepare.cppm:1389`). A project that declares nothing inherits the `PATH`
378+ mcpp was started with, byte for byte.
379+
380+ ### 12.5 `envs` is not the environment a program runs in
381+
382+ Two channels are easy to confuse and are unrelated:
383+
384+ - `[xlings.envs]` is materialised into `.xlings.json` and read by xlings for
385+ the **tool** environment. mcpp has exactly one reader for it, the
386+ materialisation.
387+ - `compute_subos_env` (`src/build/execute.cppm:418`) builds the environment a
388+ built program is **run** with, and it derives from `plan.runtimeBinding` —
389+ the SubOS's own `subos_info` — not from `[xlings.envs]`.
390+
391+ A value written under `[xlings.envs]` therefore does not reach `mcpp run`'s
392+ child. Whether it should is a separate question from this proposal and is not
393+ answered here.
394+
395+ ### 12.6 What this proposal does not touch
396+
397+ `subos`, `envs`, `[indices]`, the ownership rule, the write conditions and the
398+ `PATH` contract are unchanged. The change is confined to which of the two
399+ package-shaped fields exists, and to the resolution loss §7 describes, which
400+ the merged field inherits.
0 commit comments