fix(validator): warn instead of failing when an RPC option list cannot see a reference value - #66
Conversation
…t see a reference value
A validator resolving an RPC-backed option store does not necessarily hold every
placeholder the store URL interpolates. `CallSubscenario`'s scenario field fetches
`GetScenarios?teamId={{teamId}}&scenarioId={{scenarioId}}`, and a caller without
`scenarioId` asks a narrower question than the picker that offered the value — so a
real, active scenario comes back absent and the write is rejected as
`Value 'SCN_...' not found in options.`
Measured in production over 14 days: 652 such rejections of well-formed `SCN_` values
across 195 threads, re-sent identically 73% of the time (against 14% for every other
value shape) because the value genuinely is correct.
Treat a reference type (scenario, datastore, udt, hook, ...) whose options came from an
RPC the same way `allowDynamicValues` already treats the mapper domain: warn, keep the
value, let the editor surface it as a setup error. A false rejection is unrecoverable
for the user; a false acceptance is not. Non-reference selects keep hard validation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adjusts the validation engine so RPC-resolved option lists don’t incorrectly block saves when a reference-type value (e.g. scenario) is missing from the resolved options due to missing interpolation context, aligning behavior with existing “dynamic value” tolerance by emitting a warning instead of an error.
Changes:
- Introduces
unresolvedOptionIsTolerable(...)to centralize the “warn vs error” decision for unresolved RPC options. - Extends RPC option-miss tolerance to reference types (via
isReferenceType(field.type)), while keeping non-reference selects strict. - Updates/extends scenario validation tests to assert the new warning behavior and preserve strictness for ordinary selects.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/validator.ts |
Centralizes and broadens the RPC “missing option” tolerance to include reference types, switching specific failures from errors to warnings. |
test/scenario.spec.ts |
Updates the scenario RPC case to expect valid: true with a warning, and adds a regression test ensuring non-reference selects still hard-fail. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Blast-radius measurement (production, 14 days)Sizing which rejections this actually changes, since the relax covers eight reference types and only Of 8,862
81.4% of rejections keep hard validation. The high-volume ones are ordinary RPC-backed selects — the relax keys on the declared field type, not the name — so e.g. The changed buckets are the ones showing the validator-is-wrong signature (identical value re-sent after rejection, i.e. the model compared and found itself right):
Worth noting Net: the population that could newly pass a genuinely-bad value is ~685 rejections / ~284 threads, concentrated in the fields with the highest identical-resend rates. A false acceptance still surfaces as a setup error in the editor; a false rejection ends the user's session — which is the trade this PR takes deliberately. |
Version bump to **2.0.1**. Includes: - #69: the inactive branch of a boolean toggle no longer leaks into `schemas` / `resolvedSchemas`. Those fields came out flat with `required: true`, and a consumer persisting that list as the module's resolved form then had the field demanded at run time by validators that never see the toggle. With `fillDefaults` filling the toggle to `false`, this was hitting every module that predates the toggle. Validation outcomes are unchanged. - #66: an RPC-backed option list that cannot see a reference value warns instead of failing. Patch, not minor: no new surface, two fixes. README carries the note. Once merged, creating the `v2.0.1` GitHub release triggers the npm + JSR publish workflows.
https://make.atlassian.net/browse/MAIA-1316
Why
A validator resolving an RPC-backed option store does not necessarily hold every placeholder the store URL interpolates.
CallSubscenario's scenario field fetches:A caller without
scenarioIdasks a narrower question than the picker that offered the value, so a real, active scenario comes back absent and the write is rejected:…while
GetScenariosreturned that exact option (active: true) andGetInputInterfaceaccepted the same value. In one production thread the user picked the option from Make's own inline picker and the validator still denied it exists.Impact (measured, 14 days of production traffic)
SCN_values across 195 threads.CallSubscenariothe blast radius is the whole module: the subscenario's input interface only resolves onceparameters.scenariois set, soexpectstays empty and every field mapping is rejected too.What changed
An RPC-backed reference type (
scenario,datastore,udt,hook,account,keychain,aiagent,device) whose value is absent from the resolved list now warns instead of erroring — the same treatmentallowDynamicValuesalready gives the mapper domain. Reuses the existingisReferenceTypeguard; the four duplicated relax predicates are collapsed into one helper.Scoped deliberately: non-reference selects keep hard validation. A false rejection is unrecoverable for the user; a false acceptance still surfaces as an ordinary setup error in the editor.
Tests
test/scenario.spec.ts— the RPC-backed scenario case now assertsvalid: truewith a warning (previously a hard error; assertion updated deliberately), plus a new test proving a non-reference select resolved from the same RPC still hard-errors, so the relax cannot silently widen. Verified the first fails against the pre-fix validator and the second passes both ways. Full suite: 479 passed.Follow-up (not this PR)
Why
GetScenariosreturns a narrower list when called withoutscenarioIdis server-side and tracked in MAIA-1316 — this change unblocks users but does not answer it.🤖 Generated with Claude Code