forms+investigations: allRequiredEngaged is flat and string-blind, the registrar's namespace is not the cost, and the post-commit tail has no seam (refs #793, refs #791, refs #789) - #795
Merged
Conversation
…blind (refs #793) `forms.hpp`'s file comment offers `return morph::forms::allRequiredEngaged(*this);` as the model `validate()` body, thirty lines under a heading that reads "Nested aggregates (recursive, cycle-safe)". That heading is about `schemaJson<A>()`. The readiness helper is flat, and its "non-quantity members are not checked" sentence does not say which ordinary member types that sweeps up. Measured on this revision, with a probe compiled against the real header (clang 22.1.8, glaze v7.4.0): EmptyCapableField<std::string> = 0 EmptyCapableField<std::int64_t> = 0 EmptyCapableField<std::vector<int>> = 0 [id set, empty name] hand-written `id.hasValue() && !name.empty()` = 0 allRequiredEngaged = 1 [optional<Sub> engaged, Sub's own required id empty] hand-written `!b.has_value() || b->validate()` = 0 allRequiredEngaged = 1 [Sub by value + vector<Sub>, their ids empty] allRequiredEngaged = 1 Both facts are already stated precisely in `docs/spec/forms/forms.md`; neither was stated where an action author meets the helper. This adds them to the `@par Readiness helper` block and to the function's own Doxygen, and changes no code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW
Yaraslaut
force-pushed
the
lane/793-791-789-investigations
branch
from
September 24, 2026 00:42
067fe5f to
9b6daea
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Three investigations, one commit. Each ticket asked a question before it asked
for code, and in each case the cheapest plausible answer — "the framework
already provides this and nobody found it" — was tested first. It held once,
partly held once, and failed once. Full findings are on the three issues; this
is the summary and the one change.
The change — refs #793
forms.hpp's file comment offersallRequiredEngaged(*this)as the modelvalidate()body thirty lines under a heading that reads "Nested aggregates(recursive, cycle-safe)". That heading is about
schemaJson<A>(). Thereadiness helper is flat. The
@par Readiness helperblock and the function'sown Doxygen now say so, and say that the "non-quantity members are not checked"
exclusion sweeps up
std::string— the single commonest thing a rung'shand-written body tests. Comment-only;
docs/spec/forms/forms.mdalreadystated both facts precisely, and is unchanged.
#793 step 1 — is
allRequiredEngagedmissing, or just undiscoverable?Neither. It is discoverable and it is present; it answers a narrower
question than the rungs ask.
Classifying all 103 single-expression
validate()bodies across the sevenrungs (script-parsed, and the total matches the issue's own 103):
allRequiredEngaged?return true, action has no empty-capable membertrue— no gainstd::string::empty()(alone or with an id)std::stringhas nohasValue()isValidYearMonth,>= 0)Not a single body was found that checks fewer ids than the action has, so
the 39 swap with zero behaviour change.
It does not recurse. Measured with a probe compiled against the real
header — not read off the source:
"Newer than the rungs" is ruled out: the helper predates rung 0 by four weeks.
"Undocumented" is ruled out: five places name it, including
examples/IMPLEMENTATION.md's own rule, which is what a rung author reads.#791 — why is the registration macro's namespace anonymous?
The premise is inverted, and the namespace is not the cost. A namespace-scope
constobject already has internal linkage in C++; the anonymous namespaceadds nothing to that. Two TUs including a header with a bare
const bool x = f();link fine, and both run the initialiser —nmreportsb(local BSS) either way.Removing the anonymous namespace from
MORPH_DETAIL_REGISTER_ACTION_LOCALandrecompiling kanban's
board_model.hppwith the rung's own-O3command:19.19 → 19.21 CPU-s, best of two each. The registrar symbols stay
b.The cost is that the registrar's initialiser expression is written in the
header, so
registerActionOnce<M, A>and its glaze children instantiate inevery including TU whatever the linkage is. Internal linkage must stay — with
external linkage the same header would be a duplicate-symbol link error. So no
edit to that macro can reach the 26.93 CPU-s, and the ticket's own remedy —
move the registrars to one TU — is the only thing that can. That is a
public-contract change; written onto the issue, not attempted here.
#789 — does the architecture already answer the post-commit question?
Direction 2 is already answered and the rung helper is redundant.
ActionDispatcher's runner (registry.hpp:687) andBridge::executeVia'slocalOp (
bridge.hpp:2128) bothcatch (const std::exception&)— everyexception, not only domain errors — append
Outcome::Failed, and rethrow. Thatis
logFailureForCurrentException, in the framework, since before the rung.The candidate answer "post-commit work is another dispatched action" is
architecturally unavailable.
concurrency_and_lifetimes.mdstates theopposite invariant: "A model cannot reach the framework that runs it." There is
no seam for a handler to dispatch anything.
Direction 1 is not answered — and the framework's own site has it.
Reproduced with a sink that throws on the
Succeededentry, asIActionLog'scontract says a failing sink must:
The mutation is durable, the caller is told it failed, and the journal records
the successful write as
Failed.recordActionSuccesssits inside thetrywhose
catchexists to report failures.Gates
clang-format --dry-run -Werror(22.1.8) — cleanclang-tidy-diff.pyvsorigin/master...HEAD, 1 file, 135 warningssuppressed by the line filter (so the changed lines were analysed) — rc 0
--target doc,WARN_AS_ERROR = FAIL_ON_WARNINGSconfirmed in thegenerated Doxyfile — rc 0
ctest— 3048/3048 passed, 220.64 s🤖 Generated with Claude Code
https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW