Skip to content

fix(routes): report what was checked, and check what was reported - #1069

Open
bloosqr wants to merge 2 commits into
jorgepb96:mainfrom
bloosqr:pr/chem-route-report
Open

bloosqr wants to merge 2 commits into
jorgepb96:mainfrom
bloosqr:pr/chem-route-report

Conversation

@bloosqr

@bloosqr bloosqr commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Three reporting faults in the route check, all found by running a long route and reading what came back rather than by reasoning about the code.

A model's own drawing was read as a species list

A model may draw its route through a capability fence and hand-write the SVG inside it. The species parser read that drawing: one Byproducts: label inside a picture that was cut off before its closing tag claimed every character to the end of the answer, and two fragments of markup became species of that step.

Nothing resolves markup, and a step with an unresolved species is emptied by design — so the step was reported as impossible to build although the author's own list was complete and correct. It also cost a correction round, because the fragments were handed back to the model as species to repair.

maskDrawnRegions blanks any capability fence and any raw <svg> with equal-length whitespace, so offsets into the answer still address the original text and the in-place species rewrite is unaffected. An unclosed picture is blanked to the end of its fence. isNameLikeSpecies drops a parsed name that is markup rather than a name.

Measured on the answer that produced the fault: the step parsed 7 species including 2 markup fragments before, and its 5 declared species after.

An unbuilt step was reported as a failed check

Nothing is checked on such a step, so counting it among the steps that fail both overstates the route's problems and hides what the author has to fix. It is now its own state, counted separately in the verdict, and it names the species whose structure could not be resolved. The application already had those names; only the step line omitted them, which made a real diagnosis slow.

A chiral building block of the wrong configuration was invisible

Such a block has the same formula, the same atom counts and the same constitution as the intended one, so balance and continuity both pass and no deterministic check can see the difference. Where the package measures the configuration at a free acid's nitrogen-bearing stereocentre, the route check now reports it beside what the author's own name asserts.

Reported, never judged: which letter belongs to a given series flips when a sulfur-bearing branch outranks the carboxyl, so the letter alone proves nothing. The field is optional and absent unless measured, so an older package simply produces no such line.

Where each structure came from

The answer now says whether a structure came from the built-in dictionary, a reference service, or the answer itself. The per-species source was already carried on every resolved species and never surfaced, so a run could not tell an offline dictionary hit from a network lookup or from a structure the model drew itself — which is the category a wrong structure hides in.

Every fragment reaches the equation

buildRouteSteps discarded a repeated fragment, so that two salts sharing an ion could not put the same token on one side twice. The set was per role, so calcium chloride written as [Ca+2].[Cl-].[Cl-] lost a chloride — and any salt with repeated counterions (magnesium bromide, sodium sulfate, potassium carbonate) could then never balance. The author would be told a correct equation was wrong.

Writing each distinct ion once also left its count to the solver, and a free ion's coefficient is the solver's to choose, so a wrong equation could be rescued by it. groupedSideTrace already knew this and worked around it in the display — "its solved count is arbitrary (often 1:1); the salts fix it instead" — which meant the report could show the author's salts while the verdict had been computed on something else.

Every fragment of every species is now written out, in order. The package regroups them from the labels it already receives, so each declared species takes one coefficient, the ratio the author wrote survives, and the verdict is computed on the species the report shows. The package side is NodusResearch/nodus-research-skill-marketplace#48; a package that cannot regroup sees the duplicates and reports several possible equations, which is visible rather than silent.

A placeholder is named as one

A species line reading see prose, as above or see step 2 is a placeholder, not a name a resolver could ever turn into a structure — seen live, on a step whose Byproducts line read exactly that. The step report and the correction prompt said "give that species a name a reference resolves, or its structure", which invites the author to invent a structure for something that has none. They now say what the author actually did and ask for the species to be listed.

Checks

npm run typecheck clean; node --test scripts/test-molecule-inspection.mjs 104/104 with 6 new tests, including one built from the real answer shape that caused the parser fault.

Note for review

This touches shared/moleculeInspection.ts and electron/ai/researchAssistant.ts, which #1066 and #1065 also touch. It is written against main and applies cleanly there; if those merge first I will rebase. The configuration report pairs with the package-side measurement in NodusResearch/nodus-research-skill-marketplace#48, and degrades to nothing without it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LVYxvTRGs6MrP7LsQaAJKz

Three reporting faults, all found by running a long route and reading what came back.

A model may draw its own route through a capability fence and hand-write the SVG inside it. The
species parser read that drawing. One role label inside a picture that was cut off before its
closing tag claimed every character to the end of the answer, and two fragments of markup became
species of that step; nothing resolves them, and a step with an unresolved species is emptied by
design, so the step was reported as impossible to build although the author's own list was
complete. It also cost a correction round, because the fragments were handed back as species to
repair. maskDrawnRegions blanks any capability fence and any raw <svg> with equal-length
whitespace, so offsets into the answer still address the original text, and an unclosed picture is
blanked to the end of its fence. isNameLikeSpecies drops a parsed name that is markup, not a name.

An unbuilt step was reported as a failed check. That overstates the route's problems and hides
what the author has to fix, because nothing was checked on that step at all: it is now its own
state, counted separately in the verdict, and it names the species whose structure could not be
resolved. The application already had those names; only the step line omitted them.

A chiral building block of the opposite configuration has the same formula, the same atom counts
and the same constitution as the intended one, so balance and continuity both pass and no
deterministic check can see the difference. Where the package measures the configuration at a
free acid's nitrogen-bearing stereocentre, the route check now reports it beside what the
author's own name asserts. Reported, never judged: which letter belongs to a given series flips
when a sulfur-bearing branch outranks the carboxyl, so the letter alone proves nothing. The field
is optional, so an older package simply produces no such line.

Finally, the answer now says where each structure came from -- the built-in dictionary, a
reference service, or the answer itself. The per-species source was already carried on every
resolved species and never surfaced, so a run could not tell an offline dictionary hit from a
network lookup or from a structure the model drew itself, which is the category a wrong structure
hides in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVYxvTRGs6MrP7LsQaAJKz
…s named as one

buildRouteSteps discarded a repeated fragment, so that two salts sharing an ion could not put the
same token on one side twice. The set was per role, so calcium chloride written as its ions lost a
chloride, and any salt with repeated counterions -- magnesium bromide, sodium sulfate, potassium
carbonate -- could then never balance: the author would be told a correct equation was wrong.

Writing each distinct ion once also left its count to the solver, and a free ion's coefficient is
the solver's to choose, so a wrong equation could be rescued by it. groupedSideTrace already knew
this and worked around it in the display -- "its solved count is arbitrary (often 1:1); the salts
fix it instead" -- which meant the report could show the author's salts while the verdict had been
computed on something else.

Every fragment of every species is now written out, in order. The package regroups them from the
labels it already receives, so each declared species takes one coefficient, the ratio the author
wrote survives, and the verdict is computed on the species the report shows. A package that cannot
regroup sees the duplicates and reports several possible equations, which is visible rather than
silent.

Separately: a species line that reads "see prose", "as above" or "see step 2" is a placeholder, not
a name a resolver could ever turn into a structure. The step report and the correction prompt said
"give that species a name a reference resolves, or its structure", which invites the author to
invent a structure for something that has none. They now say what the author actually did and ask
for the species to be listed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVYxvTRGs6MrP7LsQaAJKz
@bloosqr bloosqr changed the title fix(routes): a picture is not a declaration, and an unbuilt step says so fix(routes): report what was checked, and check what was reported Oct 6, 2026

This branch has not been deployed

No deployments
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.

1 participant