Uh oh!
There was an error while loading. Please reload this page.
feat(service-automation): definition-level input-schema refusal is non-retryable (FLOW_INPUT_SCHEMA_INVALID) - #13371
Conversation
…n-retryable (FLOW_INPUT_SCHEMA_INVALID) Fixes#10025 Implements the maintainer ruling (2026-08-20, Option B taken whole): the definition-level input-schema guard's verdict is a pure function of the flow definition, so execute() now classifies its throw as a never-dispatched exit - typed InputSchemaViolationError recognised in the catch, stamped with the spec-registered FLOW_INPUT_SCHEMA_INVALID code (contract half: #11504), no status, and no handoff to retryExecution. One refusal row in the run log instead of 1 + maxRetries identical ones. The #9889 parity floor underneath is untouched: both attempt paths still call the guard identically. The FlowRefusalCode transcription row maps the code to 422 in the shared classifyFlowRefusal table, so all three doors answer it identically. The trigger-refusal docs table and the SDK never-dispatched enumeration gain the member in the same change, ending the docs-side declared-not-enforced gap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
📓 Docs Drift CheckThis PR changes 2 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 26 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0d4b57142da72a98be960e8c89cc7f3d723aa0d6 && git checkout 0d4b57142da72a98be960e8c89cc7f3d723aa0d6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c38b7eff7816751f5b7c45cd5de5cb094a5262cb 207fd0dbcf166b942f6f644e49a496ea2ce6b2eb && git checkout -B drift-repro c38b7eff7816751f5b7c45cd5de5cb094a5262cb && git merge --no-ff 207fd0dbcf166b942f6f644e49a496ea2ce6b2eb
node scripts/docs-audit/affected-docs.mjs --json c38b7eff7816751f5b7c45cd5de5cb094a5262cb
|
Uh oh!
There was an error while loading. Please reload this page.
zhuangjianguo
commented
Aug 30, 2026
Director seat (session Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The comment-only hunk this branch adds to packages/runtime/src/domains/automation.ts inserted 7 net lines of doc comment above the anonymous-deny seam, shifting the `ec.isSystem` read from line 982 to line 989. Row 52 of content/docs/permissions/system-context.mdx still anchored 982, so check-system-context-census reported the pair it reports for pure line rot: [site-without-a-row] automation.ts:989 reads `ec.isSystem`, no row anchors it [anchor-is-not-a-read-site] the page anchors automation.ts:982, which is not a read site Repaired with the gate's own sanctioned path, `node scripts/check-system-context-census.mjs --fix`, which rewrote exactly one anchor and nothing else. The population did not move: the census still finds 109 elevation read sites in 20 packages across 45 files, the page already declares 109, and no [declared-count] problem was reported before or after. No count was hand-edited and no census row was hand-written or deleted. No changeset -- a docs-anchor repair inside this branch adds no user-visible change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10025
Implements the maintainer ruling (2026-08-20, Option B taken whole, recorded on the card): a definition-level input-schema refusal is non-retryable and classifies as a never-dispatched exit. The contract half landed first (#11504, PR #12611); this PR is the producer half plus the two docs rows the spec seat deliberately withheld until a producer existed.
What changed
Engine (
packages/services/service-automation/src/engine.ts)validateNodeInputSchemasnow throws a typedInputSchemaViolationError(module-private) instead of bareErrors — the classification channelexecute()'s catch needs, so no message prose is ever sniffed. The bug(service-automation): node input-schema validation guards only the FIRST attempt — a retry attempt runs the nodes attempt 1 refused to run #9889 chokepoint contract is untouched: both attempt paths still call the guard identically.execute()'s catch short-circuits before thestrategy: 'retry'handoff when the throw is the guard's: it returns{ success: false, code: 'FLOW_INPUT_SCHEMA_INVALID', error }with nostatus(the automation: bothtriggerroutes still answer HTTP 200 wrapping an inner {success:false} — the same #3962 residue #8684 closed on resume #9378 never-dispatched class, besideFLOW_DISABLED/FLOW_NO_START_NODE) and never entersretryExecution. Exactly one failed run-log row is written where the parity floor alone wrote1 + maxRetriesidentical ones.Transcription row (
packages/runtime/src/flow-dispatch-status.ts)FlowRefusalCodegains'FLOW_INPUT_SCHEMA_INVALID'andclassifyFlowRefusalgains its 422 arm — transcribing the spec-recorded inputs (code string, 422 mapping, never-dispatched semantics) registered by spec: widen AutomationResult.code + register a never-dispatched ADR-0112 code for the definition-level input-schema refusal — the contract half of #10025's ruled Option B #11504; nothing minted here. All three doors (trigger,/actions, declared endpoints) read this one table, so they answer identically with no door edits.Tests
input-schema-retry-parity.test.ts: the twoexpect(result.status).toBe('failed')assertions are inverted totoBeUndefined()plusexpect(result.code).toBe('FLOW_INPUT_SCHEMA_INVALID'), the run-log row count drops from 3 to 1 (the retry-budget evidence), and the header paragraph that explained "why there is nocode" — now stating the opposite of what the file asserts — is retired and replaced with the ruled classification. The third test (a schema-valid flow with a transient downstream failure still retries normally,runs.count === 2) is deliberately untouched.endpoint-flow-dispatch-status.test.ts: the new row is asserted per-row (422 + code + producer message at the endpoint door), in the distinguishable-set pin (statuses[404, 409, 422, 422, 400], five distinct codes — two rows now share 422 by design), and in the three-door parity loop.Docs (landing WITH the behaviour, per the recorded split)
content/docs/automation/flows.mdx— the trigger-refusal table gains the422 FLOW_INPUT_SCHEMA_INVALIDrow (now at the five-outcome table around line 1442) with the non-retryable remedy text.content/docs/api/client-sdk.mdx— the never-dispatched enumeration and theerr.codeunion comment gain the member (around lines 720-741).Changeset (patch,
@objectstack/service-automation+@objectstack/runtime) states the deliberate behaviour change plainly: one refusal row instead of1 + maxRetries, no retry budget or backoff consumed,422 FLOW_INPUT_SCHEMA_INVALIDwhere this exit previously answered400 FLOW_FAILED.Evidence
engine.tsreverted to the branch base (3ec8646f1, parity floor only) and the new assertions in place, tests 1-2 went red exactly as predicted (result.codeundefined vs'FLOW_INPUT_SCHEMA_INVALID'), test 3 stayed green. Restore proven by blob hash (git hash-object== HEAD blobeba2d9a12c,git diff HEADempty). The parity test resolves./engine.jsas an in-package relative import (source, notdist/), so no rebuild was needed for either leg; the mutation leg was confirmed on disk (InputSchemaViolationErrorgrep count 0 under the reverted tree).208562689:@objectstack/service-automation91 files / 1091 tests passed;@objectstack/runtime201 files / 2970 tests passed; runtimetypecheckclean (note: its tsconfig excludes**/*.test.ts, so the edited test file is outside that program — it is exercised by vitest and the DTS build instead). Both packages rebuilt with DTS success.scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, answer derived at commit208562689): all ~50 matched families run locally and green, includingcheck:dispatcher-error-vocabulary,check:error-code-casing, the docs-audit family,check:engine-double-contract,check:where-matcher,check:type-check-debt(--re-measure: 30 entries, none above recorded) andcheck:dual-build-cjs-loads(102 entry points load). Repo-widepnpm lint(eslint, full population — no narrowing) exit 0. Two gates exited 3 = PREREQUISITE NOT MET, i.e. NOT MEASURED locally, left to CI:check-test-completeness.mjs(needs CI shard attestations) andpm/check-half-states.mjs(needs board-sweep context).Scope notes
execute()does with the guard's verdict, not who renders it.Generated by Claude Code
Generated by Claude Code