Skip to content

Restructure mechanism parser (std::expected, separation of public/private headers) and support in-code mechanisms#276

Merged
boulderdaze merged 48 commits into
mainfrom
269-stabilize-parse-with-stdexpected
Jun 17, 2026
Merged

Restructure mechanism parser (std::expected, separation of public/private headers) and support in-code mechanisms#276
boulderdaze merged 48 commits into
mainfrom
269-stabilize-parse-with-stdexpected

Conversation

@K20shores

@K20shores K20shores commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #269

  • Made the development parser the v1 parser
  • refactored so that we can call parse (which validates schema when parsing)
  • and so that we can call validate which checks for structural things (all species on a reaction are in a phase)
  • now we return std::expected<Mechanism, Error>
  • and there is only one mechanism, which all versions parse to
  • added the notion of public/private headers, so most headers got moved to src/detail
  • bumped the major version to 2 since this is a breaking api change
  • updated the docs
  • added an example to the readme
    • also cmake extracts this code and runs it as part of every make test call
  • dropped support for particle/model/modal stuff since this is meant to be what we use for miam, but that work will be pulled in from https://github.com/NCAR/MechanismConfiguration/tree/develop-something-ambitious
  • added a changelog to the docs
  • refactored so that schema validation is separate from correctness validation (a species in a reaction exists in a phase)
  • renamed most validation things to schema checks
  • added an intermediate representation for checking structrual validation of a mechanism
    • this allows in-code mechanisms to be checked for correcentess, and for parsed files to be checked for correctness while still allowing them to return errors that contain file location information

K20shores and others added 26 commits June 11, 2026 09:33
Add an `exactly_one_of` group to ValidateSchema so a schema can require
exactly one of a set of keys, then use it to let reaction components
reference their species with either the canonical `name` or the legacy
`species name` alias (used by v1 config files): exactly one, error if both
or neither.

- ValidateSchema: defaulted `exactly_one_of` param. None present ->
  RequiredKeyNotFound; more than one -> MutuallyExclusiveOption. Group
  members count as allowed keys (never flagged invalid).
- validation.hpp: add `species_name = "species name"`.
- ValidateReactantsOrProducts requires exactly one of {name, species name}.
- New GetReactionComponentName() helper; route the component-name reads
  across all reaction validators and the parser through it.
- Tests + fixtures: AcceptsSpeciesNameAlias, RejectsBothNameAndSpeciesName.

Prep for consolidating development -> v1 (issue #269): keeps community v1
files (which use `species name`) parseable by the development engine.

Also adds planning docs: PLAN-consolidate-v1.md, TODO-name-species_name-alias.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate onto a single v1 engine (issue #269). The two-phase
Validate()/Parse() "development" engine becomes the canonical v1
implementation; the old combined v1 parser is removed.

- Delete old src/v1 combined parser (parser.cpp, mechanism_parsers.cpp,
  utils.cpp, reactions/*_parser.cpp) and its headers (parser.hpp,
  mechanism_parsers.hpp, reaction_parsers.hpp, validation.hpp, utils.hpp).
- git mv src/development/* -> src/v1/* and include/.../development/* ->
  include/.../v1/* (sources, headers, reactions/{parsers,validators} tree,
  CMakeLists), preserving history.
- Sweep namespace development -> v1 and mechanism_configuration/development/
  includes -> v1/. Old v1::validation (with its own species_name) is gone;
  the engine now resolves unqualified validation:: to the shared top-level
  namespace.
- Drop add_subdirectory(development) from src/CMakeLists.txt; remove the
  emptied development dirs and a dead, unreferenced reaction_types.hpp stub.

Library builds. Tests are not yet migrated (Stage 4): test/unit/v1 still
calls the old API and test/unit/development references the gone namespace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the throwing stub with a real version dispatch returning
std::expected<Mechanism, Errors>:
- directory input or a missing version field -> v0 parser
- major 0 -> v0, major 1 -> v1 engine (FileToYaml -> Validate -> Parse)
- unsupported versions -> InvalidVersion
Add parse(const std::string&) for in-memory v1+ documents.

Remove the dead f() and commented-out dispatch; re-enable the v0 include;
fix parse.hpp (drop the duplicated declaration, add the string overload).

validate(const Mechanism&) (the in-code path, Option 2) and test migration
are still pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Engine / parse():
- Engine now accepts major version 1 (MAJOR_VERSION 1, not 2), since the
  renamed engine IS the v1 parser.
- Add v1::Parser::ResolveFileConfig(): ports the multi-file "{ files: [...] }"
  logic from PR #256 to the unified engine. species/phases/reactions sections
  may be split across files (v1.1+); they are merged into one inline node, then
  validated/parsed normally. Inline sections pass through unchanged. Structural
  checks (missing 'files' key, non-array/object section, minor-version >= 1)
  and file-load errors are reported with the config path prefix.
- parse() v1 case now goes ResolveFileConfig -> Validate -> Parse.
- Drop the parse(const std::string&) content overload: it hijacked
  parse(std::string path). parse(filesystem::path) accepts strings implicitly.

Tests:
- development fixtures renumbered 2.0.0 -> 1.0.0; development test sources
  swept development:: -> v1::, includes development/ -> v1/.
- v1 community tests migrated from v1::Parser::Parse(path) to top-level parse().

Status: file-list mechanism verified (its structural sub-tests pass). Remaining
14 failures are a single separate gap: community v1 lists phase species as bare
strings ([A, B, C]) but the engine expects objects ([{name: A}]). Tracked next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The old v1 phase parser accepted a phase's species as either bare strings
(`species: [A, B]`) or objects (`species: [{name: A, ...}]`). The development
engine only handled the object form and threw on scalars. Restore the dual
handling in both ParsePhases (parse) and ValidatePhases (validate): a scalar is
shorthand for the species name; a map carries name + optional properties.

Valid v1 reaction/phase configs now parse. Remaining v1 test failures are a
separate matter (stale error-count expectations + a few validator key-set gaps
such as FIRST_ORDER_LOSS dropping `products`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oducts, surface condensed_phase

Reconcile the engine's per-reaction validators/parsers with v1 community files:

- Reaction components may be bare strings (e.g. `gas-phase species: A`), not just
  objects. GetReactionComponentName handles scalars; ParseReactionComponents only
  reads coefficient/comments on the object form (scalar -> name only).
- first_order_loss: restore `products` (optional key + parse it, guarded since
  optional). The type already had the field; old v1 allowed it.
- surface: `condensed_phase` is optional, not required (community surface files
  omit it). Validator only verifies the phase when present; parser reads it when
  present.

No more segfaults. Remaining v1 failures are stale test expectations (the engine
reports richer errors than old v1), to be updated next per decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These reactions are supported by CAMP (v0) and aren't used by the ODE solver,
so remove them from the v1 engine (re-addable later atop the canonical types):
aqueous_equilibrium, condensed_phase_arrhenius, condensed_phase_photolysis,
henrys_law, simpol_phase_transfer, wet_deposition.

- Delete their parser + validator sources and CMake entries.
- Remove their IReactionParser classes + registry entries from reaction_parsers.hpp.
- Remove their type structs + Reactions members from types.hpp (species-level
  henrys_law_constant_* properties are unrelated and kept).
- Delete all development tests (they had been swept onto the v1 engine during the
  rename and were the only coverage for these now-removed reactions).

Unused validation:: keys for the dropped reactions are left in place (harmless).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-list version-check ordering

- v1 community tests: update error-count expectations to the engine's richer
  reporting (unknown species -> ReactionRequiresUnknownSpecies +
  RequestedSpeciesNotRegisteredInPhase; duplicates report one error per
  occurrence). Drop branched's stale third bad-component assertion.
- ResolveFileConfig: check the file-list minor-version requirement BEFORE loading
  referenced files and return early, instead of also emitting file-not-found
  errors for a config already known to be invalid (matches old v1).

Suite now 25/26. The lone remaining failure (file_configs TwoPhasesSets) is a
semantic-policy question: a gas-phase reaction produces a species defined only in
the aqueous phase, which the engine's phase-membership check rejects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A reaction's products may legitimately reference species defined in another phase
(e.g. a gas-phase reaction producing an aqueous species), so only reactants are
required to belong to the reaction's phase. Reactants stay phase-checked; products
are still required to be known species (unknown-species check unchanged).

- All reaction validators build a reactants-only list for CheckSpeciesPresenceInPhase;
  products go only into the unknown-species check. emission (products only) drops the
  phase check entirely; first_order_loss already checked reactants only.
- Realign v1 test error-count expectations accordingly.

Full suite green: 26/26.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Semantic validation belongs to the canonical Mechanism, not to a specific parser
version. Add a top-level validate(const Mechanism&) -> Errors that checks the
mechanism's semantic invariants on the struct, independent of how it was produced
(parsed from any version, or built in code):
  - unique species names; unique phase names; unique species within a phase
  - phase species exist in the species list
  - reaction species exist; reactants are registered in the reaction's phase
    (products may reference any phase)

This is the in-code validation path from issue #269's "ideas" (in-code mechanism
-> validate). New unit tests cover an in-code mechanism directly.

Note: additive. parse() still uses v1's YAML-level (line-numbered) validation;
routing parse() through validate(Mechanism) and removing v1's duplicated semantic
checks is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…termediate

Introduce semantics::Input (species/phases/reactions as name + optional
ErrorLocation) and ValidateSemantics(Input) as the single place the semantic
rules live. validate(const Mechanism&) now builds a location-free Input and calls
it. Errors carry line:col whenever a source location is supplied — which lets the
parse path reuse the same checker with locations next, instead of duplicating the
rules in v1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts species/phase/reaction species references from a fully-resolved v1 YAML
node, each carrying its source ErrorLocation, into the version-neutral
semantics::Input. This lets the parse path feed the single ValidateSemantics
checker with line:col (the YAML counterpart to validate(Mechanism)'s location-free
build). Not yet wired into Parser::Validate — that wiring + removing v1's own
semantic checks is the next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…'s copy

v1's parsers now own only deserialization + structural (schema/cardinality/
mutually-exclusive) validation. All semantic checks (unknown species, phase
membership, duplicates, unknown phase) are performed once by the version-neutral
ValidateSemantics:

- Parser::Validate runs the structural validators, then calls
  ValidateSemantics(BuildSemanticInput(object)) so parse-time semantic errors keep
  line:col.
- The 12 reaction validators drop their species/phase/duplicate checks (keeping
  ValidateSchema, ValidateReactantsOrProducts, cardinality, Ea-vs-C).
- ValidateSpecies / ValidatePhases reduced to structural schema checks.

Semantic rules now exist in exactly one place and apply to the canonical Mechanism
(parsed or in-code). The old per-component semantic helpers in v1/utils are now
unused (left in place; can be removed in a follow-up). Suite: 27/27.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the integration tests onto the current public API:
- test_parser: use top-level parse() for version dispatch (v0 fallback, v1,
  missing file, unsupported version, v0 directory). Drop the v2/development case.
- test_v0_parser: v0::Parser::Parse still returns expected; update to the
  canonical Mechanism + .error().
- test_v1_parser: files via parse(); in-memory YAML/JSON via the v1 engine
  (Validate + Parse(node)), since v1::Parser no longer has Parse(path)/
  ParseFromString/ParseFromNode.
- test_v1_read_from_file_configs: multi-file configs via parse().
- Drop the stale development_parser CMake entry (source no longer exists).

Known: examples/v1/full_configuration.{json,yaml} is semantically invalid under
the engine's checks (undeclared foo/bar/baz; third-body M used outside the gas
phase), so the two full-configuration tests fail pending an example-data decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The full-configuration example tripped the engine's semantic checks:
- third-body species M was used as a TROE reactant but absent from the gas phase
  -> add M to the gas phase species list
- the TERNARY_CHEMICAL_ACTIVATION reaction referenced undeclared species
  foo/bar/baz -> use declared species (A/B/C)

Species count is unchanged (M was already declared), so the integration test
assertions are unaffected. Full suite green: 31/31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After semantic validation moved to the version-neutral ValidateSemantics, these
v1/utils helpers had no remaining callers: FindUnknownObjectsByName,
CheckSpeciesPresenceInPhase, CheckPhaseExists, ReportUnknownSpecies,
FindDuplicateObjectsByName, GetSpeciesNames, and the NodeInfo/DuplicateEntryInfo
structs. Remove them. v1/utils now holds only the parser-support helpers still in
use: AsSequence, AppendFilePath, GetComments, GetReactionComponentName.

Full suite green: 31/31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icles

- Rewrite the docstrings to reflect that these are structural (schema) checks
  only; semantic invariants moved to ValidateSemantics.
- Remove ValidateParticles (header + definition): its only callers were the
  Henry's-law/SIMPOL validators, which were dropped with the CAMP-only reactions.

Suite green: 31/31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header holds the schema key-name constants (the `validation::` namespace), not
validation logic. Renaming disambiguates the three similarly-named headers:
  - validation_keys.hpp  : the key strings
  - validate_schema.hpp  : structural/schema checking
  - validate.hpp         : semantic validation (ValidateSemantics / validate)
The `validation` namespace name is unchanged; only the file + its include sites
(7) are updated. v0/validation.hpp is unrelated and untouched.

Suite green: 31/31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…echanism)

A single, compile-checked example showing the two entry points around the
canonical Mechanism: parse() a YAML/JSON file (version auto-dispatched, returning
std::expected<Mechanism, Errors>), and build a Mechanism in code then validate()
it with the same semantic checks the parser uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the v1 private headers to the joined `mechanism_configuration::v1`
namespace form and normalize include ordering. Fixes a typo in
type_validators.hpp (`::v`) that broke compilation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g notes

- Add docs/source/v1/reactions/lambda_rate_constant.rst and wire it into the
  v1 reactions index/toctree (it was the only v1 engine reaction undocumented).
- Add a LAMBDA_RATE_CONSTANT reaction to examples/v1/full_configuration.{yaml,json}
  so the example exercises every v1 reaction type (both parse cleanly).
- Remove the scratch planning docs PLAN-consolidate-v1.md and
  TODO-name-species_name-alias.md from the repo root.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump project version 1.1.2 -> 2.0.0 to reflect the breaking C++ API changes
on this branch (std::expected parse()/validate(), canonical Mechanism, the
public/private header split with a single umbrella header, and removed v1
reactions). The v0/v1 config file formats are unchanged.

Add docs/source/changelog.rst (modeled on music-box's changelog) covering the
API changes, header reorganization, removed reactions, and a migration example,
and wire it into the docs toctree. Update the docs |project_version|.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@K20shores K20shores linked an issue Jun 13, 2026 that may be closed by this pull request
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 12.50000% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 31.57%. Comparing base (f3203c4) to head (1b7f7d5).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
include/mechanism_configuration/version.hpp 0.00% 4 Missing ⚠️
include/mechanism_configuration/errors.hpp 0.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #276       +/-   ##
===========================================
- Coverage   70.00%   31.57%   -38.43%     
===========================================
  Files           5        4        -1     
  Lines          10       19        +9     
===========================================
- Hits            7        6        -1     
- Misses          3       13       +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

K20shores and others added 2 commits June 13, 2026 09:26
`exclude` was nested under `strategy` instead of `strategy.matrix`, which is an
invalid workflow definition — GitHub Actions rejected it at startup, so the
Ubuntu job failed in 0s on every push. Re-indent `exclude` under `matrix`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two separate stdlib/include issues that GCC papered over via transitive
includes but Linux clang and MSVC did not:

- MSVC: errors.hpp defines PrintTo(..., std::ostream*) and streams a string
  into it, but never included <ostream>, leaving std::basic_ostream an
  incomplete type (C2027 in <string_view>'s operator<<). Add <ostream>.

- Linux clang: clang-18 paired with libstdc++ (gcc-13/14) does not expose
  std::expected at any -std flag (reproduced in ubuntu:24.04). Build the
  Ubuntu clang job against libc++ (-stdlib=libc++ + libc++-dev), which
  provides std::expected and std::format. Verified: full repo builds clean
  with clang-18 + libc++ for both fmt OFF and ON under -Werror, 31/31 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
K20shores and others added 15 commits June 15, 2026 09:22
…for semantics

The word "validate" was overloaded: the public semantic validate(const Mechanism&)
/ ValidateSemantics shared the name with the v1 structural YAML checks, making it
hard to see what the real validation is. Reserve "validate" for semantics and name
the structural layer "CheckSchema":

- Parser::Validate            -> Parser::CheckSchema
- IReactionParser::Validate   -> CheckSchema (+ all 12 reaction overrides)
- ValidateSpecies/Phases/Reactions/ReactantsOrProducts
                              -> CheckSpeciesSchema/CheckPhasesSchema/
                                 CheckReactionsSchema/CheckReactantsOrProductsSchema
- ValidateSchema primitive    -> CheckSchema
- detail/validate_schema.{hpp,cpp} -> detail/check_schema.{hpp,cpp} (+ src/check_schema.cpp)

In-member primitive calls are qualified (mechanism_configuration::CheckSchema) to
avoid the member name hiding the free function. Fix the stale validator docstrings
that claimed to "ensure all referenced species and phases exist" (that moved to
ValidateSemantics) and mark the now-unused existing_species/existing_phases params.

validate()/ValidateSemantics and the validators/ + type_validators.* names are
unchanged. Build clean (gcc-14 -Werror), 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reaction validators carried an is_valid flag that was either never read
(arrhenius) or only gated a trailing `return errors;` that the function reached
anyway. Where it did guard a later check (first_order_loss/surface/photolysis
cardinality, and CheckReactionsSchema's per-reaction loop), it was equivalent to
`!errors.empty()` at that point, so the flag is replaced by that check.

Also rename the reused local `validation_errors` -> `schema_errors`, matching the
CheckSchema naming and avoiding the "validation" overload (reserved for the
semantic validate()/ValidateSemantics).

No behavior change. Build clean, 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions/schema/

These hold structural schema checks (CheckSpeciesSchema, XxxParser::CheckSchema,
etc.), not semantic validation, so "validators" was misleading. Rename to match
the CheckSchema naming and pair cleanly with the sibling reactions/parsers/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each reaction was one IReactionParser subclass (e.g. ArrheniusParser) whose two
methods, CheckSchema and Parse, were split across reactions/schema/X.cpp and
reactions/parsers/X.cpp with near-identical include blocks. Splitting one class
across two files/dirs cost cohesion for no real benefit.

Merge each pair into a single src/v1/reactions/X.cpp holding the whole parser
(CheckSchema then Parse), drop the parsers/ and schema/ subdirectories, and list
the 12 sources directly in reactions/CMakeLists.txt. (taylor_series needed
<detail/constants.hpp>, previously only pulled in on the parser side.)

No behavior change. Build clean (gcc-14 -Werror), 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hema

Parse(node) now runs CheckSchema (structure + semantics) itself and returns
std::expected<Mechanism, Errors>, so callers can't build from an unvalidated
node or forget to validate. CheckSchema and the construction step (Build) are
now private. The top-level parse() v1 branch collapses to ResolveFileConfig ->
Parse, and the string-parsing test helper to a single Parse call.

No behavior change. Build clean (gcc-14 -Werror), 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parsing a v1 file no longer requires calling ResolveFileConfig by hand. Parse is
now overloaded:
  - Parse(filesystem::path)  -> resolve file-list sections, validate, build
  - Parse(std::string)       -> treat as a YAML/JSON document, validate, build
  - Parse(YAML::Node, bool)  -> validate + build a loaded node

ResolveFileConfig, CheckSchema, Build, and the config-path setters are now
private; the dead FileToYaml is removed. The top-level parse() v1 branch
collapses to `return v1::Parser{}.Parse(config_path);`, matching v0. Exception
safety moved into Parse(node) (the shared path), so value-conversion throws
surface as Errors rather than escaping.

No behavior change. Build clean (gcc-14 -Werror), 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The github/license shields.io badge is dynamic (hits the GitHub API) and
intermittently fails with "Unable to select next GitHub token from pool",
rendering broken. Switch to a static Apache-2.0 badge that never calls the API,
and point the link at blob/main/LICENSE (the default branch) instead of master.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The boolean only told CheckSchema whether to reset config_path_ (the file-path
prefix for error messages) — a flag argument that callers had to reason about.
Each Parse overload already knows its context, so it now sets config_path_
itself (ResolveFileConfig for files; empty for string/node) and delegates to a
private flag-free ValidateAndBuild(node) that runs CheckSchema + Build. CheckSchema
and Parse(node) lose the bool.

No behavior change. Build clean, 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The comment claimed version "1.3.0" / "minor != 1", but the config is "1.0.0"
and the gate is `minor < 1` (file-list requires v1.1+). Correct the comment to
match the config and logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CheckSchema also ran ValidateSemantics, so its name undersold what it did and
re-overloaded "validate" in the structural layer. Move the semantic pass up into
ValidateAndBuild, which now reads as an explicit three-phase pipeline:
structural (CheckSchema) -> semantic (ValidateSemantics, gated on clean
structure) -> Build. CheckSchema is now schema-only, matching its name.

No behavior change. Build clean, 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The validation:: namespaces were just dictionaries of YAML key-name strings,
used by parsing and construction as much as by checking (e.g. Build reads
object[validation::gas_phase]) — so "validation" misdescribed them. Rename
mechanism_configuration::validation -> keys and v0::validation -> v0::keys
(validation_keys.hpp -> keys.hpp, v0/validation.hpp -> v0/keys.hpp), and
validation:: -> keys:: throughout (~600 refs). "validation" is now reserved for
actual semantic validation.

No behavior change. Build clean (gcc-14 -Werror), 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
detail/keys.hpp held the v1 keys but lived at the detail root, asymmetric with
v0's detail/v0/keys.hpp. Its only cross-version use was parse()'s version
dispatch reading the "version" field. Move it to detail/v1/keys.hpp as
mechanism_configuration::v1::keys (v1 call sites are inside namespace v1, so
keys:: still resolves with no change), and have the version-agnostic dispatcher
in parse() read the literal "version" instead of depending on a version's keys.

Now each version owns its key vocabulary: v0::keys and v1::keys.
No behavior change. Build clean (gcc-14 -Werror), 31/31 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Unsupported version number" error was the one parse() error without a file
path. We only reach that branch after reading the version out of the file, so it
always names a real document — prefix it (path: message) like the other errors,
so it points at the offending file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GetVersion now also returns the source location of the `version` field (absent
for directories / version-less docs). parse()'s default branch uses it to format
the error as path:line:col error: ... , matching the located v1 errors, instead
of just the file path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@K20shores K20shores requested a review from boulderdaze June 16, 2026 16:24

@boulderdaze boulderdaze left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I have a few questions and suggestions

Comment thread include/mechanism_configuration/errors.hpp Outdated
Comment thread include/mechanism_configuration/errors.hpp Outdated
Comment thread include/mechanism_configuration/errors.hpp
Comment thread include/mechanism_configuration/parse.hpp Outdated
Comment thread include/mechanism_configuration/parse.hpp Outdated
Comment thread src/detail/v1/keys.hpp Outdated
Comment thread src/detail/v1/keys.hpp Outdated
Comment thread src/detail/v1/keys.hpp Outdated
Comment thread src/detail/v1/parser.hpp Outdated
Comment thread src/detail/v1/parser.hpp Outdated
@boulderdaze boulderdaze changed the title 269 stabilize parse with stdexpected Restructure mechanism parser (std::expected, separation of public/private headers) and support in-code mechanisms Jun 17, 2026
@boulderdaze boulderdaze changed the title Restructure mechanism parser (std::expected, separation of public/private headers) and support in-code mechanisms Restructure mechanism parser (std::expected, separation of public/private headers) and support in-code mechanisms Jun 17, 2026
@boulderdaze

Copy link
Copy Markdown
Contributor

I updated the title to capture some of the context from the comments for the release notes. Hope that's okay with you!

@boulderdaze boulderdaze merged commit 10fc4d7 into main Jun 17, 2026
28 of 29 checks passed
@boulderdaze boulderdaze deleted the 269-stabilize-parse-with-stdexpected branch June 17, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stabilize parse() with std::expected

3 participants