2026.8.26.2 — the answer was already resolved, and nothing consulted it - #512
Merged
Conversation
Two defects the previous release's family does not cover. There the predicate asked a narrower question than the one it was given; here the predicate asked the right one, got the right answer, and the answer never reached a decision. Both are a recorded field with no decision-side reader. `requires` was checked, never applied. `prepare.cppm` has a block titled "the toolchain, resolved now that the graph exists"; it scans `provides` to decide the compiler and does not scan `requires_`, which is collected a thousand lines later and used only to reject the outcome. Measured with llvm@22.1.8 already installed: `mcpp build` refused and advised `mcpp toolchain default llvm` — a global change, for one project's dependency — while `MCPP_TOOLCHAIN=llvm@22.1.8 mcpp build` finished in 1.02s. Selecting it writes nothing, and that is where the decision sits rather than a rule to remember: `resolve_target_toolchain` has two call sites, both after the graph, so the first-run install-and-persist branch and all three `write_default_toolchain` calls are downstream. On a machine with no toolchain the branch is not even entered — its condition is `!tcSpec.has_value()`. Refusal now happens only where the project stated its own compiler, and the advice points at that statement; two packages requiring different families is an error naming both. The tier gate asked about the identity, not the request. `parse` fills a missing env segment lexically so the identity stays total, and `envExplicit` exists to record that it was a fill — the gate did not read it. `--target aarch64-linux` refused as `aarch64-linux-gnu` (planned) while `aarch64-linux-musl` built, and `riscv64-linux` was reported `unknown` although the family is registered. A request that declined the segment is now completed against the vocabulary, with the lexical default tried first so the rule retires itself. `parse()` is unchanged: identity must stay lexical, total and host-independent. Two diagnostics stopped lying. `unknown target` no longer fires when the (arch, os) group is non-empty, refusals quote the spelling the user wrote, and the unknown path finally records a code instead of reporting `other`. `why toolchain --format json` gave two answers for the C library — `cLibrary` said glibc/payload while `layers[].c-abi` said musl/graph, and the artifact (static, no interpreter, no DT_NEEDED, 11 openkal symbols) settled it. Adds `cLibrary.suppliesTarget` rather than renaming a field, per docs/11 §6. Criteria: e2e 299-303 classify through `--format json` and run on all four build hosts via the target-matrix invariants layer. 301's criterion is the sha256 of config.toml, not a successful build — those can both be true, which is the behaviour being removed. 299's second half is the control that `x86_64-linux` is still gnu.
…the claim
`compiler.chosenBy` carries the selection origin into the machine interface:
a consumer asking why a build resolved llvm had to parse the status line, which
is the substring matching that document exists to remove.
Three test defects found by reading, not by failing:
- the capability-row refusal offered "depend on a package that supplies this
target's system" — a remedy a capability pin ignores by construction, and
the sentence directly above it already said so. Split by row kind.
- 303 read `suppliesTarget` through jq's `//`, which returns its right side
when the left is null OR FALSE. `false` is an answer; it read as absent,
and windows-x86_64 reported the field missing when it was there.
- 281 asserted the old global remedy, which the only refusal that still
reaches it cannot be fixed by.
303's third half stacks a musl c-abi over the host's own target and not every
host stacks that; granted by reason, with linux-x86_64 as the denominator.
Two branches persist a default — the Windows first-run diversion, whose condition is `tcSpec.has_value()`, and the MSVC repair, whose gate is "mcpp chose this itself". A compiler chosen by `requires = ["mcpp:compiler=…"]` satisfies both, so a bare Windows box building ONE llvm-requiring project would have written llvm as the MACHINE's default and handed it to every later project that asked for nothing. Found by reading the two call sites against the rule, not by a run: it needs a Windows machine with no toolchain. `tc_origin_may_persist` gives the rule a name both sites call and a unit test can state — the e2e that hashes config.toml runs where a toolchain is already configured and never reaches either branch.
`[toolchain] default = "system"` means "whatever is on PATH" — a deliberate opt-out of the payload model. Replacing it with a payload because a dependency named a family defeats what was asked for, and mcpp cannot even tell whether the requirement is already met: the family of a PATH compiler is not knowable from the spec. check_requirements reports the mismatch against what the driver turned out to be, which is the only place that answer exists.
An unnamed branch reports `other` in the machine interface, and this release exists partly because one of those had a perfectly good name. Both paths in `resolve_required_family` — an unrecognised family, and a family with no version to use — now record `compiler-requirement-conflict`, whose comment is widened to the four situations it covers: what a consumer does about each is the same.
301's whole claim is that `config.toml` is byte-identical. macOS has no `sha256sum` — it is `shasum -a 256` — so on that host the first draft would have compared "" with "", which are equal, and reported the criterion met while measuring nothing. A build can succeed AND rewrite the configuration; that is the pair this half exists to separate. Picks whichever hasher exists, fails when neither does, and refuses an empty result on either side.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Analysis:
.agents/docs/2026-08-26-resolved-but-not-consulted.mdTwo defects, and they are not the family
2026.8.25.xclosed. There the predicate asked a narrower question than the one it was given. Here the predicate asked the right question, got the right answer, and the answer never reached a decision.Both are a field recorded with no decision-side reader — invisible when reading the criterion, visible the moment a user asks "you already knew; why do I have to say it again".
A —
requireswas checked, never appliedprepare.cppmhas a block titled "the toolchain, resolved now that the graph exists". It scanspkg.manifest.providesto decide the compiler. It does not scanrequires_— that is collected a thousand lines later and used only to reject the outcome.Measured on 2026.8.26.1,
llvm@22.1.8already installed:Nothing was missing. The remedy printed changes the default for every project on the machine because one project's dependency asked.
⭐⭐ Selecting it writes nothing, and that is the position rather than a rule.
resolve_target_toolchainhas exactly two call sites, both downstream of the graph — so the first-run install-and-persist branch and all threewrite_default_toolchaincalls sit on a branch this selection no longer enters (its condition is!tcSpec.has_value()).config.tomluntouchedRefusal now happens in exactly one situation: the project stated its own compiler in⚠️ In that situation the global default is not what is being used, so the old
[toolchain]or[target.X].mcpp toolchain default llvmadvice could not fix the failure it was printed under; it now names the statement that decided. Two packages requiring different families is an error naming both, not a pick by traversal order.B — the tier gate asked about the identity, not the request
parsefills a missing env segment lexically so the identity stays total;envExplicitexists precisely to record that the fill was a fill — and the gate did not read it. The question asked was aarch64, Linux; the question answered was aarch64-linux-gnu, and the message quotes a triple absent from the command.examples/06-openkal-crossteaches the short spelling for three platforms; the fourth was the one that could not be written.A declined segment is now completed against the vocabulary, lexical-default-first so the rule retires itself when
aarch64-linux-gnugraduates.parse()is unchanged — identity must stay lexical, total and host-independent, and its unit test is untouched. Blast radius is two(arch, os)cells; every other lexical default already names a supported row.C — two diagnostics stopped lying
unknown target 'riscv64-linux'was false:riscv64-linux-muslis registered (planned). The fill had produced a row outside the vocabulary. That path also had norefusal::record, so--format jsonreportedreason: "other"for a branch with a perfectly good name.D — the query gave two answers for the C library
cLibrarysaid glibc/payload whilelayers[].c-abisaid musl/graph, in one document. The artifact settles it — static, no interpreter, noDT_NEEDED, 11 openkal symbols. AddscLibrary.suppliesTarget; a field added, not renamed, andmodenot widened, per docs/11 §6.Criteria
--format jsonrather than substring search, wired into thetarget matrixinvariants layer — all four build hosts.~/.mcpp/config.toml, not a successful build: a build can succeed and rewrite the user's default, and that is the behaviour being removed.x86_64-linuxmust still be gnu. Testing only aarch64 would make "send every bare-linuxto musl" look correct.TripleRequest.*unit tests, including one sweeping the whole vocabulary: every supported row reachable from its own spelling.linux-x86_64as the denominator required to actually run them.Docs:
03,11,16+ zh mirrors. Version2026.8.26.2(two sites; bootstrap pin untouched).Added after the first review pass
write_default_toolchainhas three call sites; only one is gated on!tcSpec.has_value(). The Windows first-run diversion and the MSVC repair are both reachable, so a bare Windows box building one llvm-requiring project would have written llvm as the machine's default.tc_origin_may_persist(TcOrigin)gives the rule a name both sites call and a unit test states — the config-hash e2e runs where a toolchain is already configured and never reaches either branch.[toolchain] default = "system"is left alone. It means "the PATH compiler, whatever it is" — a deliberate opt-out of the payload model, and its family is not knowable from the spec.⭐
compiler.chosenBy = {origin, requiredBy, replaced}added towhy toolchain --format json, so "why llvm" is answerable without parsing the status line. 301 asserts on it rather than on prose.//returns its right side when the left isfalse, so a boolean field read as absent (windows-x86_64 reported it missing while it was there — usehas()); 301 measured its baseline in the runner's start directory instead of the project under test, so every assertion passed while the requirement had changed nothing; 281 asserted the old global remedy.Verified at the artifact, not the exit code
Compatibility
⭐ No build that previously succeeded changes behaviour. Where the graph declares a compiler, the previous outcome was a
check_requirementsrefusal — those builds did not succeed.--target aarch64-linuxandriscv64-linuxwere refusals.x86_64-linux/x86_64-windows/riscv64-none/aarch64-macoscomplete exactly as before, held by a unit test that sweeps the whole vocabulary. The machine interface only gains fields.Ecosystem
All seven openkal repos built from this branch — 29 legs, each logging
under review: mcpp 2026.8.26.2 (from fix/resolved-but-not-consulted).