Repository navigation
Description
Filing gate: ① product defect — reproduced through a public HTTP door, landing file named.
Path: automation.trigger-status-contract (checklist item priority P1; found while driving its steps[5], outside its clauses — no clause failed)
QA-source: #24545 · automation.trigger-status-contract · steps[5] (incidental, outside clauses)
Type: Bug — violates the resume route's own declared closed-set / no-silent-drop contract.
Reader: the domain:cli lane seat (owner of packages/runtime) — first step is the resume arm of packages/runtime/src/domains/automation.ts (~3197–3262).
Duplicate check (open AND closed): "flow resume body both inputs and variables, variables silently ignored" → 3 hits (#22642, #7639, #3853 — none match; #3853 is a different, closed resume-body hole) · "resume route variables alias inputs precedence dropped" → 1 hit (#3853, no match) · "resume body closed set unknown key inputs variables output branchLabel" → 0 hits.
reach: POST /api/v1/automation/showcase_reassign_wizard/runs/RUN/resume with both inputs and variables (the latter carrying an unknown screen field) → 200 success:true; the variables bag is never read. Measured on stock showcase at 5b14b7f.
What happens
The resume route accepts inputs, variables, output and branchLabel as a closed key set and type-checks each. When a body carries BOTH inputs and variables, the assembly reads b.inputs ?? b.variables, so variables is discarded without a word: the call answers 200 and the run completes, even when variables holds a field the screen does not declare. Sent alone, the same variables bag is judged (and refused) normally — so this is not a dead alias, it is a live key dropped only in combination.
Reproduction (stock showcase, framework 5b14b7f)
- Boot showcase (
objectstack dev --seed-admin), sign inadmin@objectos.ai/admin123(POST /api/v1/auth/sign-in/email). GET /api/v1/data/showcase_task?limit=1&fields=id,assignee→ task id T.POST /api/v1/automation/showcase_reassign_wizard/triggerbody{"recordId":"T","objectName":"showcase_task"}→ 200status:"paused", runId R, screencollectwith required fieldnew_assignee.POST /api/v1/automation/showcase_reassign_wizard/runs/R/resumebody{"inputs":{"new_assignee":"inputs-win@example.com"},"variables":{"new_assignee":"variables-lost@example.com","bogus_key":1}}.- Expected: a 400 refusal naming the conflicting keys (or, at minimum, the
variablesbag judged — which alone yields 400 INVALID_SCREEN_INPUT forbogus_key). - Actual: 200
success:true; the run completes;GET /api/v1/data/showcase_task/Tshowsassignee: "inputs-win@example.com".
- Expected: a 400 refusal naming the conflicting keys (or, at minimum, the
- Controls (fresh paused runs):
{"variables":{"new_assignee":"v@example.com","bogus_key":1}}→ 400 INVALID_SCREEN_INPUTUnknown screen field "bogus_key";{"variables":{"new_assignee":"variables-only@example.com"}}→ 200 and the value is written.
Contract
packages/runtime/src/domains/automation.ts:3135—variablesis in the accepted resume key set and is type-checked (~3231).automation.ts:3152-3163(closed-set note, commit a433122) and:3197-3203(value-shape note): the route states that silently dropping a submitted value while answering 200 is the defect family it closes.- No published schema declares the resume body; the client SDK names only
inputs/output/branchLabel, andvariablesis an undocumented accepted alias. The MCPresume_rundoor builds the signal from one source only, so it is not affected.
Mechanism and fix landing
packages/runtime/src/domains/automation.ts:3259—const inputs = (b.inputs ?? b.variables);- Fix: refuse a body carrying both
inputsandvariableswith the route's 400 validation-failure envelope (or merge and refuse on conflict) beforeresume(); pin it beside the existing closed-set / value-shape tests inpackages/runtime. - Severity hint: low — a caller has to send both keys; no data outside the caller's own run is affected.
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage: p2 ·
bug·domain:cli·area:workflow·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T14:11ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. The 18.0 QA sweep found this at 5b14b7f (or the hotcrm next.5 bump). Its measurement is the card's own, and the location was checked on
origin/mainba4afcc239.variableswheninputsis also sent, which breaks its own no-silent-drop contract. It is reached through a public door, and an author sending one of the two is unaffected.domain:cli:packages/runtime/src/domains/automation.ts, the resume arm. Refuse a body that carries both, naming the one key to use, or define one precedence. The lane picks and says which on this card.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 6 ·
domain:cliseat 1 · p2 · 2026-10-19T14:44ZSession:
session_01SYEJqDL3NXxrJRBVqaMu9TAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24546-resume-inputs-variablesWorktree:
objectstack-issue-24546Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)The lane's pick, as triage
6269278052asked: refuse. A resume body that carries bothinputsandvariablesanswers the route's own400validation-failure envelope. It names both keys and says to sendinputsalone, andresume()is never called. The reasons:automation.tsabout:3152–:3163and:3197–:3203) name a silent drop under a 200 as the defect family it closes.File surface (read on
origin/mainde4352460; line numbers move; stop on a breach and explain it in the report):packages/runtime/src/domains/automation.ts: the resume arm's body checks (about:3135–:3262; today:3259readsb.inputs ?? b.variables).packages/runtime. Its cases are both keys →400(with an ablation that restores??),variablesalone andinputsalone → as today.@objectstack/runtimein the narrowing shape. It is a narrowing because a both-keys body that answered200now answers400.packages/spec/**; the client SDK and the MCPresume_rundoor (the card reads both as unaffected); every other route; retiring thevariablesalias itself (measure its producers and report them, do not remove it).Container & model:
S,mode:subagent,model: default tier(dispatch-gates.mjs --tier: "no path-derived mandate"); ceilingCONTRACT_REVIEW_TIERfor the one review owed before the queue (reason: it narrows a public door's accepted bodies)Clause-②: no (narrowing)
Basis for the Clause-② line: no key or shape is added. A body the route used to answer 200 while discarding one of its values now answers 400.
Responsibility: this lane's own code produces the risk (
packages/runtime/src/domains/automation.ts). No platform path covers it. It reaches any caller ofPOST /api/v1/automation/:name/runs/:runId/resumethat sends both keys; it was measured on stock showcase by the card.Thread-read: 6269278052
Serial constraints cleared:
automation.ts.pm:dispatchedclaim names it.pm:blocked(Blocked-by: #10757), not dispatched, and touches other files.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{
"issue": 24546,
"status": "done",
"branch": "claude/issue-24546-resume-inputs-variables",
"pr": "#24594",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T — mode:subagent, the parent seat's harness-stamped id (container CLAUDE_CODE_REMOTE_SESSION_ID, as the relay route read it)",
"premise_still_valid": true,
"summary": "Premise held at origin/main 2667e24: automation.ts:3259 still read
b.inputs ?? b.variablesafter the closed-set and value-shape checks. The resume arm now refuses a body carrying bothinputsandvariables. It throws the route's own validationFailure (400 VALIDATION_FAILED, details.fields locatinginputsandvariableswith ADR-0114 catalog codeinvalid_value, message naming both keys and saying to sendinputsalone). The refusal sits after the unknown-key and value-shape refusals, whose messages are unchanged, and before refuseUnrelatedResume and resume(), so the engine is never consulted.undefinedcounts as absent, and the assembly line is unchanged. Either key alone behaves as before, and the alias is not retired. FILE-SURFACE NOTE (not a scope expansion, same defect): the claim listed one new test file, butautomation-resume-value-shape.test.tscarriedit('keeps the variables alias, and inputs still wins when both are sent'), whose second half pinned exactly the removed behaviour. That half was removed, the alias half kept and renamed, with a pointer to the new file. The PR body states this. Census ofvariablesproducers on this route: in objectstack at eb51ced, none outside runtime's own alias pins (http-dispatcher.test.ts:490-495, automation-resume-envelope.test.ts:189-195, automation-resume-value-shape.test.ts:95 and :255-263). Client SDK (index.ts:5906, :8237), verify handle.ts:866, dogfood, docs and examples sendinputs. MCP resume_run (mcp.ts:1000-1001) builds the signal in process. In objectui at main 8aa8e3e and at pin 4016370 (read-only shallow clone in the scratchpad), the only producer is FlowRunner.tsx:446-447, which sends{ inputs }. Deviations: (1) the first full-suite launch spelledtest -- --maxWorkers=2, so vitest dropped the flag. It was stopped by its own process group (pgid 24155) within seconds and relaunched asexec vitest run --project local --maxWorkers=2; the cited reading is that run. (2) A scratch measurement file was in the tree for about 20 s whilecheck:query-options-erasureran, so its first run died ENOENT (exit 1). It was re-run after removal, exit 0. (3) The commit carries the model-free trailer pair AGENTS.md prescribes (Co-authored-by: Claude), not the harness reminder's model-named line, and no card trailer. (4) origin/main gained 2 commits after the branch point (5a871e4 core/metadata-protocol request rounds, ffba02e spec describe text), with no path overlap with this diff. Not merged: the dispatch limits merges to os-dev.md's reasons, and none applies. (5) The relay answered UNCONFIRMED (exit 6, run 39278536516 still readqueued), but the target read shows PR #24594 opened by objectstack-fleet[bot] at 15:31:02Z: draft, head eb51ced, assignee zhuangjianguo, body read back byte-identical, one footer. It was not re-dispatched. Labels documentation, size/m, tests and tooling were set by other actors (the labelers), not by this run, and were left alone. (6) Standard-text conflict, named rather than resolved silently: os-dev.md says repo-wide scans (pnpm lint) and the declared-wide families are CI's and not owed, and the dispatch asked for them. They were run, all green, and the reading is in gates.","tests": "HEAD eb51ced. New file packages/runtime/src/domains/automation-resume-inputs-variables.test.ts, verbose run:
Test Files 1 passed (1) · Tests 10 passed (10). Targeted run of the new file + value-shape + envelope + http-dispatcher.test.ts:Test Files 4 passed (4) · Tests 309 passed (309). Full @objectstack/runtime local project (pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2, lock VERDICT command-exit 0, held 711s on a shared box):Test Files 423 passed (423) · Tests 6655 passed | 19 skipped (6674). Therepoproject (test:repo) is not touched by the diff and is not named by the derivation, so it is declared to CI Test Core.pnpm --filter @objectstack/runtime typecheck: exit 0,check:test-typecheck: OK — 27 file(s) / 190 error(s) / 68 pinned signature(s) held, so the new test file compiles clean. ABLATION, on the committed fix:node scripts/ablation-replace.mjs --delete(WRAP mode, restore armed) with the whole refusal block as anchor, leavingb.inputs ?? b.variablesas before. The mutation landed: anchor x1 to x0, blob 455f28ad to bed62029. New file + envelope + value-shape gaveTests 5 failed | 56 passed (61). Red were exactly the 5 both-keys cases; the card body readwas accepted (answered {status:200, … output:{assignee:inputs-win@example.com}}). The 5 other new cases (ordering, undefined-absent, and the 3 controls) and every envelope and value-shape case were green. Restore:blob after restore 455f28ad == blob at HEAD,git diff HEAD empty. The subject is imported by relative path (src), so no dist leg applies. BEFORE/AFTER THROUGH THE ROUTE: a real Hono socket (LiteKernel + HonoServerPlugin port 0 + createDispatcherPlugin prefix /api/v1, signed in, scripted collect-screen engine) with the card body. At base 2667e24 (automation.ts blob 65c9cffd, swapped into the tree only, then restored withgit checkout HEADand the hash verified) it answered 200 success:true, and the engine was handed{variables:{new_assignee:inputs-win@example.com}}. At head eb51ced it answered 400 VALIDATION_FAILED with fields [inputs invalid_value, variables invalid_value], and the engine was never called. Controls were identical at both:variablesalone with bogus_key gave 400 INVALID_SCREEN_INPUT (the engine got the bag),variablesalone valid gave 200, andinputsalone valid gave 200. The scratch harness was deleted and is not in the diff.","gates": "Local, at HEAD eb51ced after the final commit. Derivation
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(stderr: tree objectstack-ai/objectstack at eb51ced, 4 paths vs merge base 2667e24) gave 67 commands. Each was run with its exit captured before any pipe. First pass: 65 exit 0.pnpm check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET, no full build), andpnpm check:query-options-erasureexited 1 (ENOENT on my transient scratch file, deviation 2). After a fullturbo run build --filter=!@objectstack/docs --concurrency=2(Tasks 76 successful, 76 total; lock VERDICT command-exit 0), those two plus every dist-reading derived gate were re-run, all exit 0: check:dts-closure (76 built packages swept), check:lean-entry-closure, check:published-files, check:sourcemap-no-sources-content, check-dts-emitted --self-test.node scripts/pm/dispatch-gates.mjs --ran ran.list: exit 0,67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. Key verdict lines: check-adr-0087-registration gave[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription), accepted. check-empty-changeset:No empty-frontmatter changeset introduced (1 declaring changeset(s) added). check:nul-bytes: OK, 12371 files. check:doc-authoring: clean. The 10 declared-wide families were all exit 0: check:init-service-contract, live-db-isolation, meta-type-normalized, optional-error-sink, resume-authority-declared, runner-env-posture, settings-bind-window, startup-registry-verdict, verify-stand-in, wildcard-fallthrough.pnpm lint(full union, eslint . --no-inline-config): exit 0, no findings, 138s. eslint --no-inline-config over the 3 changed .ts files: exit 0. Runtime typecheck: exit 0. NOT MEASURED locally, outside the derived total by the derivation's own account: the 4 workflow-variable commands (check-issue-citations --census and the three check-shard-attestation --emit jobs), reason: their argv carries CI-only values. CI on PR #24594, read once at report time: all 31 check-runs on eb51ced readqueued(Actions backlog), including Build Core, Test Core (1/6..6/6), Type Check · workspace/source gates/consumer gates/debt ledger, Lint · ESLint/PM dispatch-gates/repo & tooling gates/docs & code guards/CI, release & engine gates, Check Changeset and Governed Surface Queue Guard. Every CI family is in_progress, not re-run locally beyond the derived subset above. Review owed: the seat owes one CONTRACT_REVIEW_TIER review before the queue, stated in the PR body. The dev did not run it.","mcp_calls": "0 — no MCP GitHub tool was called; reads went through gh REST (GET only)",
"api_writes": "2 — both through the fleet-write relay (scripts/pm/fleet-write/dispatch.mjs, route dispatch, session_01SYEJqDL3NXxrJRBVqaMu9T), each one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24594/assignees [zhuangjianguo], no labels sent; (2) comment, POST /repos//issues/24546/comments (this os-dev-report). Plus 2
git push(not REST): the empty-branch probe and eb51ced. No PR body PATCH, no label write, no card assignee write.","open_questions": [
{
"question": "Retire the
variablesalias on the resume body? It is out of scope here (claim hold-off) and is the seat's call. This run measured it.","options": [
"A — keep it. After this PR a both-keys body is refused, so the alias can no longer drop anything silently. Cost: none now, but a second undocumented spelling of one submission stays on a public door.",
"B — retire it on its own card. Measured producers outside this repo's own alias pins: zero, in objectstack at eb51ced and in objectui at main 8aa8e3e and pin 4016370. Cost: another narrowing (Clause-② no (narrowing), changeset, ADR-0087 disposition), edits to three alias pins in packages/runtime, and the 17.1 release notes keep naming it in the closed set as history."
],
"recommendation": "B as a separate low-priority card if the seat wants one spelling on the door, because the census found no producer to migrate. Otherwise A is safe now that the combination is loud."
}
],
"out_of_scope_findings": [
"class: b · reach: measured through the route's domain handler, not a socket. An operator's (posture PLATFORM_ADMIN)
POST /automation/approval_flow/runs/run_7/cancelwith body {"foo":1} answered 400 VALIDATION_FAILED with details.fields[0].code "unrecognized_keys", and the published ApiErrorSchema still parses it (details is untyped there), so no gate sees it. · evidence: ADR-0114 D2 declares FieldErrorCode a CLOSED catalog (packages/spec/src/api/errors.zod.ts:222-292: 'a service does not get to invent one — it adds a member here'), andunknown_fieldis the member for this condition (the resume door one route over uses it). Two hand-built refusals in packages/runtime put Zod or off-catalog vocabulary on details.fields[].code: packages/runtime/src/domains/automation.ts:959 (run-lifecycle cancel / restore-suspension unknown-key refusal,unrecognized_keys) and packages/runtime/src/domains/permission-set-clone.ts:194 (clone name rule,invalid_string, catalog hasinvalid_format). Seam: spec:FieldErrorCode (packages/spec/src/api/errors.zod.ts) → runtime:packages/runtime/src/domains/automation.ts:959 | runtime:packages/runtime/src/domains/permission-set-clone.ts:194. Same family as the fieldsFromZodIssues D3 mapping; one card for both sites, not two. · dedupe words: unrecognized_keys field code, FieldErrorCode closed catalog, invalid_string permission-set clone, ADR-0114 off-catalog details.fields code, run-lifecycle body refusal","carrier: none (承接者:无) · noted, not filed — docs/design/screen-flow-runtime.md:33 spells the resume route
POST /api/v1/automation/runs/:runId/resume, with no:namesegment. It is a design note, not a reference page, so it is unexercised drift, recorded only in PR #24594 Acceptance notes."]
}
objectstack-fleet commentedon Oct 19, 2026
ACCEPT (the lane seat's review) → PR #24594 comment
6271224869·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-19T15:47Z · on headeb51ced02. A both-keys resume body answers the route's own400, and single-key bodies are unchanged. The ablation reds exactly the 5 both-keys cases. The open question is answered A: keep thevariablesalias, which has no producer outside this repo's own pins and is now loud in combination. Filed: #24597. Owed before the queue: oneCONTRACT_REVIEW_TIERreview (a narrowing), run once every check of this head has concluded.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed: PR #24594 →
c3954057f·domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-19T17:06Zmerged: true, merge commitc3954057f, merged 2026-10-19T17:05Z through the merge queue (enqueued 2026-10-19T16:42Z)origin/mainc3954057fis an ancestor oforigin/main, with a single parent (squash). The 4 files and +298 / −6 match what was reviewed ateb51ced02gh-readonly-queue/main/*pr-24594*head remains6269985720→ report6271088846→ the lane seat's ACCEPT6271224869, pointer6271260665→ the contract review atCONTRACT_REVIEW_TIER, PASS (6272034994, on the same head) → ready and auto-merge.inputsandvariablesanswers the route's own400 VALIDATION_FAILED, naming both keys and saying to sendinputsalone. It no longer answers200withvariablessilently dropped.variablesalias is kept.minorwithfix(runtime)!:.details.fields[].code(unrecognized_keys,invalid_string), which ADR-0114's closedFieldErrorCodecatalog forbids #24597 (two hand-built refusals inpackages/runtimeput off-catalog codes ondetails.fields[].code).completedon the merge.pm:dispatchedis removed in the stroke after this comment.Generated by Claude Code