@@ -394,6 +394,19 @@ Today the only way to read the header back is `$cpp_options()[["USER_HEADER"]]`,
394394is why §1 can have `$cpp_options()` report `cpp_options_supplied` without losing
395395anything : the header was never really a `cpp_option` , and now it is not one at all.
396396
397+ ** `R/model.R:709` is what makes it one , and it goes. ** After resolution , `compile()`
398+ writes the header back into `cpp_options` under whichever spelling was used , and
399+ `:941` then stores that list , so a caller who passed `user_header = "inc/mine.hpp"` as
400+ an argument and never touched `cpp_options` gets `USER_HEADER = "/abs/path/inc/mine.hpp"`
401+ back from `$cpp_options()`. Wired to `cpp_options_supplied` with that line still in
402+ place , the accessor would report a field the caller never passed , holding a path they
403+ never wrote. What survives is only the Make flag : CmdStan reads `-include $(USER_HEADER)`
404+ (`make/program:41` ), so `USER_HEADER=` still has to reach `make` , and it reaches it as an
405+ injected option merged at flag construction (§4 ) rather than through the list the record
406+ is taken from. `resolved_header$spelling` goes with the line — `:709` is its only
407+ consumer , and one channel has one spelling — alongside the `previous` parameter §8
408+ removes.
409+
397410# ## One canonical spelling, established on entry
398411
399412`cpp_options_to_compile_flags()` (`R/cpp_opts.R:129` ) uppercases every named entry , so
@@ -939,9 +952,16 @@ A single "sort and last-wins-deduplicate" rule is wrong. The correct rules diffe
939952
940953A record whose `format_version` this cmdstanr does not read ** rebuilds , and says
941954so ** , exactly like an executable that predates records (§7 ). It is not refused and
942- does not require `force_recompile`. At 1.0 the readable set is exactly `{1}` , so in
943- practice any mismatch rebuilds ; a later release may widen the set , which changes what
944- is readable without changing this rule.
955+ does not require `force_recompile`. ** A release reads exactly the format it writes and
956+ nothing else ** , so in practice any mismatch rebuilds ; a later release may widen the set ,
957+ which changes what is readable without changing this rule.
958+
959+ The number is deliberately not written down here. Stage 3 starts writing records and
960+ Stage 4 bumps the format (below ), so a literal in this section would have to be kept
961+ in step with a value only the merging pull request knows — the two - owner failure that
962+ §9 exists to avoid. It costs nothing to leave open : the pre - bump format is never
963+ released , so the only records written under it are on development branches , and they
964+ rebuild once.
945965
946966** `format_version` versions the build interpretation contract , not the JSON shape. **
947967If a later cmdstanr changes how `cpp_options` are canonicalised , identical bytes in a
@@ -1183,9 +1203,17 @@ samples pays it regardless, since `$sample()` calls `$variables()` to validate d
11831203** `$format(overwrite_file = TRUE)` must stop refreshing the cache **
11841204(`R/model.R:1308-1312` ). It rewrites the Stan file and then reassigns `stan_code_`
11851205and clears `variables_` , which makes both accessors describe a source the executable
1186- was * never * built from — # 1228's failure in the opposite direction. #1235 added that
1187- refresh deliberately , under the older contract where `$code()` meant " the file as it
1188- is now" ; the accessors have since been redefined and this has not caught up.
1206+ was * never * built from — # 1228's failure in the opposite direction. Both lines were
1207+ written under the older contract where `$code()` meant " the file as it is now" ; the
1208+ accessors have since been redefined and this has not caught up.
1209+
1210+ ** The two lines are not the same age , and the difference decides a NEWS entry. ** The
1211+ `variables_` clear is # 1235's, written and reverted inside the unreleased window, so
1212+ `NEWS.md:94` goes with it and no user upgrading from 0.9 ever saw it. The `stan_code_`
1213+ reassignment is commit `1719851e` from April 2022 and shipped in 0.7.0 through 0.9.0 ,
1214+ so removing it changes * released * behaviour and owes an entry of its own. `NEWS.md:94`
1215+ describes both in one sentence , which is how deleting it as a stale unreleased entry
1216+ would silently take the released half down with it.
11891217
11901218Removing those lines is the whole fix. ** `$format()` is kept , overwriting included ** —
11911219a reviewer proposed removing the method , but rewriting the file is the useful part
@@ -1719,6 +1747,19 @@ time. So this is the only place the invariant §10 relies on can be established.
17191747record must carry a parseable `builder` version, and on the fallback `<exe> info` must
17201748report complete version fields.
17211749
1750+ **"Syntactically valid" means the grammar cmdstanr already uses**: three numeric
1751+ components with an optional release-candidate suffix, `[0-9]+\. [0-9]+\. [0-9]+(-rc[0-9]+)?`.
1752+ That is not a new contract invented here — it is the pattern at `R/path.R:298` and `:337`,
1753+ where cmdstanr already decides what counts as a CmdStan version when matching installation
1754+ directories. Pointing this check at the same grammar keeps one definition doing both jobs.
1755+ Note for whoever implements it that `cmdstan_version_for_comparison()` strips `-rc[0-9]+$`
1756+ (`R/path.R:156`), so the check runs on the reported string before that stripping.
1757+
1758+ **`utils::compareVersion()` must not be the validator.** "It did not complain" is a
1759+ weaker property than "this is a CmdStan version": `compareVersion("2.36", "2.35.0")` and
1760+ `compareVersion("2.36.0.1", "2.35.0")` both return `1` with no warning and no error, and
1761+ neither input is a version CmdStan reports. Only the grammar rejects them.
1762+
17221763It is a syntactic check and nothing more. **Rejecting a version for being old would
17231764defeat this whole section**, whose purpose is that a binary built by an older CmdStan
17241765keeps working. Without the check, `model_compile_info()` synthesises `".."` from three
@@ -2057,12 +2098,11 @@ ship as one stage** — separating those leaves a window where the new promise i
20572098whichever way the cut is made (Stage 4 ), though the engine ' s pure, unwired half
20582099separates cleanly and is Stage 3b.
20592100
2060- **The release order is stated here and nowhere else.** Everything below refers to this
2061- line rather than restating it:
2062-
2063- ```
2064- Stage 4 -> Stage 5 -> NEWS reconciliation -> release candidate -> Air' s format - > 1.0
2065- `` `
2101+ **#1258 states the release order; this section states the constraints behind it.** The
2102+ split follows the boundary already set under Purpose and scope, and it has a test: if
2103+ the order changes, the work list is what you edit; if the *reason* changes, this is.
2104+ Both printing it is how four different statements of it came to exist in an earlier
2105+ draft.
20662106
20672107The candidate ships when everything is ready rather than at the earliest defensible
20682108moment, and that is a structural choice rather than a preference. The moment an item can
@@ -2073,12 +2113,29 @@ One definition of ready removes the adjudication instead of getting it right eac
20732113
20742114What downstream gives up by waiting is a tag, not a start. We open their pull requests
20752115ourselves (below), and the dev version supplies the boundary in the meantime: bumped **in
2076- the same pull request as each break ** , it lets brms write
2077- `packageVersion("cmdstanr") >= "0.9.0.9003"` against master the day the break lands ,
2078- which is finer - grained than a tag rather than a substitute for one. Bumping in a
2079- follow - up commit is worse than not bumping at all , since a guard written against the new
2080- number then takes the old branch and calls a method that has already gone. Stages 1 , 4
2081- and 5 each owe one ; Stages 2 , 3 and 3b change nothing observable and do not.
2116+ the same pull request as each break**, it lets brms guard on `packageVersion("cmdstanr")`
2117+ against master the day the break lands, which is finer-grained than a tag rather than a
2118+ substitute for one. Bumping in a follow-up commit is worse than not bumping at all, since
2119+ a guard written against the new number then takes the old branch and calls a method that
2120+ has already gone.
2121+
2122+ **Each guard names the stage it needs, not a number chosen now.** brms moves onto the
2123+ standalone family, which arrives in Stage 4, so its boundary is *the Stage 4 dev
2124+ version*, whatever that pull request assigns. Writing a literal here picks the wrong
2125+ stage as soon as the arithmetic moves: from `0.9.0.9002`, Stage 1' s bump is `.9003` , so
2126+ a guard written against `.9003` today would switch brms to `compile_stan_file()` three
2127+ stages before it exists — the partial migration this paragraph exists to prevent. The
2128+ downstream pull requests we open carry the real numbers.
2129+
2130+ ** A bump is owed for a public contract downstream has to branch on , not for anything
2131+ observable. ** Stages 1 , 4 and 5 owe one ; Stages 2 , 3 and 3b do not. Stage 3 is the case
2132+ that makes the distinction necessary rather than pedantic : it creates a sidecar beside
2133+ every executable and can print the untracked - dependency note , both plainly observable ,
2134+ and neither is something brms or instantiate could write an `if` against. A version
2135+ number exists so someone can branch on it , and there is nothing there to branch on. What
2136+ the sidecar does raise is whether instantiate carries the record into the package library
2137+ alongside the staged executable , which is install mechanics and belongs to # 1238; no
2138+ version guard would answer it.
20822139
20832140# ## Stages 0 and 1 — landing in #1235, then Make-option correctness
20842141
@@ -2204,10 +2261,16 @@ enough, and is one fewer moving part in CI.
22042261
22052262### Stage 5 — public build-record inspection
22062263
2207- `stan_build_info()` last, because its inputs do not exist until Stage 4: it reports
2208- `reported_features` and the record, and Stage 4 is what makes both live. That is an
2209- ordering fact rather than a scheduling preference, so it cannot drift. It ships before
2210- the candidate, per the order above.
2264+ `stan_build_info()` last, because **it publishes answers Stage 4 settles**. Its inputs
2265+ exist a stage earlier — Stage 3 writes the record and captures `reported_features` — so
2266+ the ordering is not about availability. It is about meaning. Until Stage 4 deletes the
2267+ merge, `$cpp_options()` still answers "what is this binary" by mixing the report into
2268+ the request, so a function published before then would arrive into a world where its
2269+ own purpose is not yet true, and Stage 4 would change what it reports. It must also
2270+ answer for an unprovenanced executable (§7), and record-aware adoption is Stage 4, so
2271+ before that the case it most needs to cover does not exist. That is an ordering fact
2272+ rather than a scheduling preference, so it cannot drift. It ships before the candidate,
2273+ per #1258.
22112274
22122275**It must ship in 1.0**, despite looking purely additive. Because §1 keeps the
22132276request separate from what the binary reports, this is the only way to ask what an
@@ -2252,9 +2315,16 @@ upgrading from 0.9 to 1.0 never saw the intermediate behaviour and does not need
22522315know it existed. This is a pass before the candidate, once the stages have settled,
22532316not something to do incrementally while the picture is still moving.
22542317
2318+ **The pass covers inherited gaps, not only this design' s. ** Release notes belong to the
2319+ release rather than to whoever caused each line , and the unreleased window already
2320+ carries user - facing changes that were never written up : measured against merge subjects
2321+ since `v0.9.0` , roughly a quarter of the merged pull requests that touch behaviour have
2322+ no entry , among them a new exported function , two new public methods and a changed
2323+ default on `loo()`. # 1258 owns the method and the list.
2324+
22552325# ## The release candidate
22562326
2257- The candidate ships after the NEWS reconciliation, per the order above , so downstream
2327+ The candidate ships after the NEWS reconciliation , per # 1258 , so downstream
22582328packages have something to migrate against rather than a release note. That is what
22592329makes §8 ' s breaking change affordable.
22602330
@@ -2486,10 +2556,25 @@ outright.
24862556The formatting and linting work is scheduled around this, and the formatter and the
24872557linter go to different places.
24882558
2489- Air ' s one-time whole-repo format (#1153) is the **last** change before 1.0. It is
2490- whitespace-only and deterministic, so shipping it after the candidate is cheap, and
2491- by then there is no branch left for it to conflict with — which there would be
2492- today, with #1235 and #1254 both open. Its PR-review action is a separate thing:
2559+ Air' s one - time whole - repo format (# 1153) is the **last** change before 1.0, **and it
2560+ is optional ** . That is the reason it goes last rather than just before the candidate.
2561+ An optional cosmetic change cannot gate a tag : put it earlier and the candidate waits
2562+ on a decision nobody has made about something that does not alter what the release
2563+ does. Last , it is skippable , and skipping it changes nothing else .
2564+
2565+ Two arguments that look like they belong here do not. Branch conflicts — real today ,
2566+ with # 1235 and #1254 both open — stop once the NEWS reconciliation merges, which is
2567+ * before * the tag , so that constraint says " not yet" and never says " after the tag."
2568+ And whitespace - only determinism makes the change cheap in either slot , so it does not
2569+ choose between them either. What decides it is that the item is a maybe.
2570+
2571+ ** One check when it runs. ** Air reformats `#'` lines like any others , so a reflow that
2572+ moves a roxygen tag regenerates `.Rd` and `NAMESPACE` differently and R CMD check will
2573+ not notice. Re - run roxygen afterwards and confirm the generated files are unchanged.
2574+ That is the whole of the residual risk in shipping it after the candidate , and it is
2575+ cheaper to check than to reschedule.
2576+
2577+ Its PR - review action is a separate thing :
24932578additive , conflicting with nothing , and most useful * during * the stages , since
24942579Stages 2 to 4 write a good deal of new code that would otherwise be formatted after
24952580the fact. Check first whether it comments on changed lines or on whole files ; if the
@@ -2612,8 +2697,10 @@ every comparison. It is wrong for a model.
26122697
26132698The guard is also narrower than " unusable" , which matters because it removes the
26142699temptation to lean on it : a malformed non - empty string never reaches the `-1` at all.
2615- `".."` errors inside `utils::compareVersion()` and `"garbage"` warns, so the function
2616- has two distinct bad-input behaviours and neither is a safe fallback. Filed as #1260,
2700+ Both `".."` and `"garbage"` error inside `utils::compareVersion()` , with `missing value
2701+ where TRUE / FALSE needed`; ` " garbage" ` emits `NAs introduced by coercion ` first. So the
2702+ bad - input behaviour is an error , sometimes with a warning in front of it , and never the
2703+ `-1`. Filed as # 1260,
26172704separately from this design : the comparison should not answer a question it was not
26182705asked , but fixing it is defence in depth rather than what closes this. That is why §7
26192706makes " an adopted executable always yields a valid version" a * checked * invariant rather
0 commit comments