Skip to content

Commit 3ac07da

Browse files
committed
design: settle what is written, and state the section it is being changed against
Section 4 is decided rather than open: the correspondence is one to one in name and in meaning, so the merged table materialises as the file's own workspace object and no deps array is derived. The decision carries a consequence for W2 — mcpp appends the target's C library to that channel itself, as a namespaced reference with a version, so a key that cannot hold a namespace would leave mcpp unable to express its own entry. Section 12 records the section as a whole: field correspondence, the three places mcpp is not a pure mirror, the ownership rule and where the file is written, subos in full, and the two environment channels that are easy to confuse.
1 parent c927106 commit 3ac07da

1 file changed

Lines changed: 123 additions & 17 deletions

File tree

.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md

Lines changed: 123 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
293294
5. **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

Comments
 (0)