Uh oh!
There was an error while loading. Please reload this page.
feat(spec): register FLOW_INPUT_SCHEMA_INVALID — the never-dispatched ADR-0112 code for the definition-level input-schema refusal - #12611
Conversation
… input-schema refusal's never-dispatched ADR-0112 code The contract half of the #10025 ruling (maintainer 2026-08-20, Option B taken whole): a definition-level input-schema refusal is non-retryable and receives its own ADR-0112 code as a never-dispatched exit, mirroring FLOW_DISABLED / FLOW_NO_START_NODE (#9378 classification). - spec: AutomationResult.code gains 'FLOW_INPUT_SCHEMA_INVALID', documented as a trigger-time refusal classified BEFORE dispatch (no status; transport maps it to 422 like FLOW_NO_START_NODE — unexecutable stored definition). - ledger: one ERROR_CODE_LEDGER row under @objectstack/runtime with the required TSDoc justification: never-dispatched semantics, non-retryable per the #10025 ruling, one refusal row instead of 1 + maxRetries identical failed rows; registered ahead of its producer by design (the #10413 to #10576 split shape) — the emitting half is #10025's and asserts this exact string by value. - tests: ErrorCode.parse / ledger-containment / no-synonym pins in error-code-ledger.test.ts; a compile-level union pin plus the status-absent never-dispatched shape in automation-service.test.ts. - generated: contract.mdx / error-code-ledger.mdx regenerated via gen:docs. - changeset (minor): states plainly that retry accounting and run-log volume change for affected flows once the services half lands (N identical failed rows become one). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012xGvxcwPRTJfA7RfjXEYA4
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 126 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 f717f43878fa3eab6361d2cd6ddaf28b9d74c15e && git checkout f717f43878fa3eab6361d2cd6ddaf28b9d74c15e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 146f448a502893df21e85e1038005cbccdf7e7ff 16a65fd843d4979504612917ad57a8bd3ee84935 && git checkout -B drift-repro 146f448a502893df21e85e1038005cbccdf7e7ff && git merge --no-ff 16a65fd843d4979504612917ad57a8bd3ee84935
node scripts/docs-audit/affected-docs.mjs --json 146f448a502893df21e85e1038005cbccdf7e7ff
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11504
The ruled contract half of #10025's Option B (maintainer ruling 2026-08-20, recorded in comment 5353928368 on #10025, provenance verbatim 「其他接受你的建议。」): the definition-level input-schema refusal becomes non-retryable and receives its own ADR-0112
codeas a never-dispatched exit, mirroringFLOW_DISABLED/FLOW_NO_START_NODE(the #9378 classification). #10025 remains open — its services half (theexecute()catch short-circuit, the test inversions) is not addressed here and stays blocked on this PR landing.What this PR does
packages/spec/src/contracts/automation-service.ts—AutomationResult.code(a closed union, the [Decision]AutomationResult.codeis a CLOSED union that cannot carryFLOW_FAILED— #8684's ruled producer-first shape landed without the spec edit #9384 ruling) gains one member,FLOW_INPUT_SCHEMA_INVALID, documented in the trigger-time-refusals block: classified BEFORE dispatch, carries nostatus, transport maps it to 422 exactly asFLOW_NO_START_NODE(unexecutable stored definition), and distinct from it on purpose — that one says the definition has nothing to dispatch, this one says a node's staticconfigcontradicts theinputSchemathe definition itself declares.packages/spec/src/api/error-code-ledger.zod.ts— oneERROR_CODE_LEDGERrow under@objectstack/runtime(alphabetical slot betweenFLOW_FAILEDandFLOW_NO_START_NODE) with the required TSDoc justification: never-dispatched semantics, non-retryable because the guard's verdict is a pure function of the flow definition, ONE refusal row instead of 1 + maxRetries identical failed rows, registered ahead of its producer by design (the/api/v1/analytics/querystill drops per-measure and dataset-levelfilteron the ObjectQL path —engine.aggregatereceives no filter at all #10413 → engine.aggregate: add per-aggregation filter to the contract — ruled half of #10413 (measure-level filters on the ObjectQL analytics path) #10576 split shape), and registered under the trigger door's package for the same reason as its three FLOW_* siblings.error-code-ledger.test.tspinsErrorCode.parse, ledger containment under@objectstack/runtime, andstandardSynonymOfanswering undefined (no waiver needed);automation-service.test.tspins the union member at compile level plus the never-dispatched shape (statusabsent).content/docs/references/api/contract.mdxanderror-code-ledger.mdxregenerated viagen:docs;check:generatedreports all 14 artifacts current.@objectstack/spec) — states plainly, per the ruling: retry accounting and run-log volume change for affected flows once the services half lands (1 + maxRetries identical failed rows become one row carrying the new code).Decisions recorded (this seat owns them per the dispatch)
FLOW_INPUT_SCHEMA_INVALIDkept (the Should a definition-level input-schema refusal be non-retryable? #9889's parity floor makes a flow burn its whole retry budget on a verdict that cannot change #10025 working name). It fits the ledger's own conventions: SCREAMING_SNAKE, domain-prefixed in the existingFLOW_*family, and not a semantic synonym of any standard-catalog member —standardSynonymOfanswers undefined on both prongs (no reason-phrase alias; FLOW is a token no standard member carries), pinned in the test. Downstream Should a definition-level input-schema refusal be non-retryable? #9889's parity floor makes a flow burn its whole retry budget on a verdict that cannot change #10025 asserts this exact string by value.packages/runtime/src/flow-dispatch-status.tsFlowRefusalCoderow: NOT included here, per the dispatch's decision rule (include iff gates or type-coherence make the minted code unlandable without it). Measured, not assumed:FlowRefusalCodeis an independent literal union with no type-level derivation fromAutomationResult.code, so the widening compiles without it (full spec suite + repo grep for exhaustive switches overresult.codefound none);check:dispatcher-error-vocabularyruns in the producers → registration direction only and is green; no gate requires a registered code to be classifiable. The split recorded on Should a definition-level input-schema refusal be non-retryable? #9889's parity floor makes a flow burn its whole retry budget on a verdict that cannot change #10025 parks that row with the services half, and the [Decision]AutomationResult.codeis a CLOSED union that cannot carryFLOW_FAILED— #8684's ruled producer-first shape landed without the spec edit #9384 concern ("a fourth row would be a spec-seat widening, never a call-site decision") is discharged here in spec: the row's three contract inputs — the name, the 422 status, the never-dispatched semantics — are now recorded in the ledger TSDoc and the contract TSDoc, so the services-half row is transcription of a spec-recorded decision, not a call-site mint./api/v1/analytics/querystill drops per-measure and dataset-levelfilteron the ObjectQL path —engine.aggregatereceives no filter at all #10413 → engine.aggregate: add per-aggregation filter to the contract — ruled half of #10413 (measure-level filters on the ObjectQL analytics path) #10576 precedent). The interim window is documented in the row's own TSDoc.Verification (all at head
16a65fd84, the final commit;origin/mainre-fetched immediately before the PR — unchanged since branch point7c0d0c395)pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2— vitest verdict line:Test Files 432 passed (432)/Tests 11503 passed (11503).pnpm --filter @objectstack/spec typecheck— all three layers green; final gate's own verdict line:check:test-typecheck: OK — @objectstack/spec's test layer compiles. Both edited test files confirmed INSIDE the tsconfig.test.json program viatsc --listFiles(not merely executed).pnpm --filter @objectstack/spec check:generated— its verdict line:All 14 generated artifacts are up to date.set -erun, lock-serialized, exit 0):check:nul-bytes,check:error-code-casing(no unlisted lowercase error codes in 4783 scanned file(s)),check:dispatcher-error-vocabulary(OK — 21 unregistered code-stamping site(s), all classified),check:engine-double-contract(OK — 689 pinned, 134 in the DEBT ledger, 3 exempt),check:where-matcher,check-adr-0087-registration(changeset is additive/minor — no disposition marker required),check-changeset-no-major,check-empty-changeset.packages/runtime), both directions: a probe assigning the new member toAutomationResultcompiles (exit 0 — impossible against the pre-change dist), and a probe assigning a bogus member fails with TS2322 whose message prints the widened nine-member union — proof the reading is the rebuilt.d.ts, not cache. Probes deleted after measurement.--no-inline-config --format json— 4 TS files linted, 0 errors 0 warnings; the 3 non-TS paths answered ESLint's own "File ignored because no matching configuration was supplied" (population statement from the config, not a guess); invariance:eslint.config.mjsstates (line ~328) noparserOptions.project/ no typed rules, so this diff cannot move any untouched file's lint verdict.Out of scope, untouched
packages/services/service-automation/**, the input-schema-retry-parity test, andpackages/runtime/src/flow-dispatch-status.ts— all #10025's half. No labels or assignees changed by this PR. Clause-② applies (public envelope addition, contract-review tier): this PR stays draft; landing is the review flow's call.Generated by Claude Code