Skip to content

fix(service-automation)!: a connector pull whose input breaks the action's declared inputSchema is refused before dispatch - #28140

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-xrfb3t
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-xrfb3t

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #28040

Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: updated here — content/docs/automation/jobs.mdx ("How a run is recorded") and packages/spec/docs/SYNC_ARCHITECTURE.md ("How a pull runs") name the new refusal; skills/**: none — git grep -n -i "connectorSource\|pullConnectorSource\|inputSchema" -- skills has 1 hit, skills/objectui/guides/plugin-development.md:113 (ComponentInputSchema, unrelated), so no skill teaches the pull door.

维护者速读

  • 改了什么:连接器拉取(job 的 pull)在调用连接器动作前,先用动作声明的 inputSchema 校验将要发送的输入(含水位参数);不合格即以 VALIDATION_ERROR 400 拒绝并点名参数,上游零调用。
  • 为什么改:automation: a connector_action call is dispatched without checking its input against the action's declared inputSchema, so a missing required parameter reaches the remote API #27995 只给 connector_action 流程节点加了这道校验,拉取入口仍把缺必填参数的请求发到远端(OpenAPI 路径占位符被原样发送)。
  • 风险与回滚:收窄(BREAKING,patch 级):原本"侥幸成功"的拉取若输入不满足声明的 schema 会转为 job 运行 failed;回滚即 revert 本 PR。节点的行为与报错文字逐字节不变。
  • 先看:packages/services/service-automation/src/connector-pull.ts(新增校验段)、src/connector-input-schema.ts(从节点原样搬出的校验器)、src/builtin/connector-nodes.ts(改为调用共享校验器)。
  • 两分钟验证:pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2 src/connector-pull.test.ts,预期 23/23 通过,其中 [#28040] 组 5 条。
  • 需要你做的:无,席位落地。

What changes

  • One checker, two doors. The inputSchema judgment the connector_action node gained in fix(service-automation)!: a connector_action call whose input breaks the action's declared inputSchema fails before dispatch, naming each parameter #28035 (automation: a connector_action call is dispatched without checking its input against the action's declared inputSchema, so a missing required parameter reaches the remote API #27995) moves out of builtin/connector-nodes.ts into src/connector-input-schema.ts, unchanged byte for byte (the moved block was diffed against origin/main's, IDENTICAL). The module exports findConnectorActionInputSchema (the descriptor read the node made), judgeConnectorInput (the violations joined as the node joined them, capped at five plus ; and N more) and TEXT_FOR_SCALAR_HINT. It is not re-exported from the package entry.
  • The node calls the shared helper. Its refusal text is assembled from the same pieces in the same order, so code, error and the cast hint are byte-identical; its existing pins (connector-input-schema.test.ts, untouched) stay green.
  • The pull door (pullConnectorSource, the door a job's pull runs) judges the input it is about to send, after the watermark parameter is added and before the handler is called. A failing input throws ConnectorPullError with code: 'VALIDATION_ERROR', status: 400 (the pair the door already uses for its other input faults) and reason: 'input_schema_mismatch', and the message names each parameter by dotted path, says nothing was pulled, and points at GET /api/v1/automation/connectors. Nothing is called upstream and nothing is written.
  • Public TypeScript surface (why Clause-②: yes (narrowing), re-derived from the diff rather than the order's no (narrowing)): ConnectorPullRefusalReason gains 'input_schema_mismatch', and ConnectorPullRegistry now extends ConnectorInputSchemaSource, which requires getConnectorDescriptors(). The automation engine (what AutomationServicePlugin passes) already has it; a hand-built registry adds it.
  • Changeset .changeset/28040-connector-pull-input-schema.md: patch, BREAKING banner, both declaration lines, ADR-0087 not-required (no-migration-prescription) marker, FROM / TO / fix.

Why

Prime Directive #10: a rule holds at every call site. Triage 6521041814 graded the pull door as the sibling call site of #27995, to be judged by the same check through one helper and refused before dispatch, naming the parameter.

Tests

  • Re-measure on main (premise holds). The three refusal pins added here, run against origin/main's connector-pull.ts (8cb8c7f4), fail with the pull must refuse, not resolve: expected undefined to be an instance of ConnectorPullError: the pull resolved, so it called the action. Tests 3 failed | 20 passed (23).
  • New pins (src/connector-pull.test.ts, [#28040] group, through the shipped pullConnectorSource with a counting handler):
    1. A missing required parameter (query.since) → VALIDATION_ERROR / 400 / input_schema_mismatch, the message names 'query.since', the handler is called 0 times, nothing is written.
    2. A missing required section → names it with the parameters it requires (missing required 'query' (with 'query.since')), 0 calls.
    3. A wrong type (query.limit: '50' against integer) → refused naming it, 0 calls; text is not converted.
    4. The input judged is the input sent: a required query.since supplied only by the watermark pulls (1 call, with the watermark value).
    5. A valid input still pulls (1 call) and writes the record.
      Every existing pull pin passes with no inputSchema declared, which is the "no schema ⇒ pull what you are given" leg.
  • Ablation (node scripts/ablation-replace.mjs, on the committed fix): if (verdict) → if (verdict && false /* ABLATION */) in connector-pull.ts, landed on disk (anchor 1 → 0, marker grep -c = 1, blob 6b358231 → 349fc8e6). Exactly the three refusal pins turned red (Tests 3 failed | 20 passed (23)); restored, blob == HEAD (6b358231da3f) and git diff HEAD is empty. The tests import ./connector-pull.js from source, so no dist/ leg applies.

Acceptance notes

  • A required parameter that only the watermark supplies is absent on a first pull (nothing stored yet), so that pull is now refused. The changeset says so and gives the fix (give it a first value in connectorSource.input).
  • The rest provider's request action declares no required and its declared types match what a pull writes, so the shipped rest pull (the integration pin) is unaffected; openapi actions are where the check bites.
  • The refusal comes after the watermark read and the object-schema read (both reads, no writes), because the judged input includes the watermark value. Nothing is called upstream and nothing is written.
  • Out of scope, seen on the dogfood battery: packages/qa/dogfood/test/api-action-door-forward.dogfood.test.ts:178 expects the ACTION_DISABLED message to contain sys_metadata_activation, but feat(spec,metadata-protocol,rest,objectql,runtime): GET /meta/object serves switched-off packaged actions beside the item, and the disabled-action refusal reads as a plain sentence #27964 (3e0fd7a8f7, an ancestor of origin/main: merge-base --is-ancestor exit 0) rewrote that refusal as a plain sentence. The pin is red on main; this diff touches no file on that path. Reported to the seat for filing.

Verification

All local readings are at head 02de39cc91.

Package test + typecheck (service-automation), connector-openapi dispatch pin
  • pnpm --filter @objectstack/service-automation build → VERDICT command-exit 0
  • pnpm --filter @objectstack/service-automation typecheck → exit 0 (includes check:test-typecheck: 0 file(s) / 0 error(s))
  • pnpm --filter @objectstack/service-automation test → Test Files 227 passed (227), Tests 2881 passed (2881), VERDICT command-exit 0
  • pnpm --filter @objectstack/connector-openapi exec vitest run --maxWorkers=2 src/input-schema-dispatch.test.ts (fix(service-automation)!: a connector_action call whose input breaks the action's declared inputSchema fails before dispatch, naming each parameter #28035's end-to-end node pin) → Tests 4 passed (4)
  • Focused: connector-pull.test.ts, builtin/connector-input-schema.test.ts, builtin/connector-nodes.test.ts, connector-pull.integration.test.ts, connector-pull-service-door.test.ts → Test Files 5 passed (5), Tests 64 passed (64)
Derived gate families (dispatch-gates) + pnpm lint
  • node scripts/pm/dispatch-gates.mjs --commands derived 92 families from 7 paths vs merge base 8cb8c7f46.
  • All 92 exit 0. Three first ran into a prerequisite (exit 3) and were re-run green once it was met: check-plugin-teardown-shape --self-test (after git fetch --unshallow), check:skill-examples and check:dual-build-cjs-loads (after the full build that check:type-check-debt runs). check:type-check-debt first queued behind the dogfood run (exit 99, not measured) and passed on re-run: 124/127 workspace packages type-checked, 0 in the DEBT ledger.
  • node scripts/pm/dispatch-gates.mjs --ran → 92 derived famil(ies) accounted for — 92 run, 0 NOT-MEASURED; pnpm lint recorded outside the derivation.
  • pnpm lint → exit 0.
  • No exported symbol is renamed or removed (the moved helpers were module-private), so check-platform-checklist --changed-from is not owed.
Dogfood battery (the narrowing arm owes it)
  • Closure pnpm --filter '@objectstack/dogfood^...' build → exit 0; pnpm --filter @objectstack/dogfood test under the lock → VERDICT command-exit 1, Test Files 1 failed | 357 passed | 3 skipped (361), Tests 1 failed | 2882 passed | 20 skipped (2903).
  • The one failure is api-action-door-forward.dogfood.test.ts > the switch holds: ban_user OFF answers 409 ACTION_DISABLED…: expected 'This action is turned off for this de…' to contain 'sys_metadata_activation'. That is the stale pin on main named under Acceptance notes, not a consequence of this diff (no automation or connector path).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: only docs/SYNC_ARCHITECTURE.md (not in files[], no generator reads it), so nothing to regenerate.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the maintainer summary is Chinese by the seat's convention).

Generated by Claude Code

…ion's declared inputSchema is refused before dispatch

The pull door judged nothing before calling the connector action, so a pull
missing a parameter the action declares required still reached the remote
API. The connector_action node's inputSchema check moves to one module,
connector-input-schema.ts, which both doors import: the node's behaviour and
messages are unchanged, and the pull door now refuses the input it is about
to send (the watermark parameter included) with VALIDATION_ERROR 400 and
reason input_schema_mismatch, naming each parameter, before the action is
called.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cxUdTtczoG6TtcncPa
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec, touching 21 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/docs/SYNC_ARCHITECTURE.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 93242324040563e9db96c98ef186948797d20cbd.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/docs/SYNC_ARCHITECTURE.md) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 93242324040563e9db96c98ef186948797d20cbd → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 344d67b3b14d1a875b73dfe3fdfe330b6da77927 — the merge of head 9cfb92dd5c26c64c570a8d025b926b36e3d79f61 into base 93242324040563e9db96c98ef186948797d20cbd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 344d67b3b14d1a875b73dfe3fdfe330b6da77927 && git checkout 344d67b3b14d1a875b73dfe3fdfe330b6da77927
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 93242324040563e9db96c98ef186948797d20cbd 9cfb92dd5c26c64c570a8d025b926b36e3d79f61 && git checkout -B drift-repro 93242324040563e9db96c98ef186948797d20cbd && git merge --no-ff 9cfb92dd5c26c64c570a8d025b926b36e3d79f61

node scripts/docs-audit/affected-docs.mjs --json 93242324040563e9db96c98ef186948797d20cbd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 93242324040563e9db96c98ef186948797d20cbd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Contract-review-claim: session_016WV4czuTWNgKR22fKv7jia · head 02de39cc91be43d2e996f37378dce2360903f42d · 2026-10-30T13:49Z

The lane seat runs the CONTRACT_REVIEW_TIER review this PR owes (Clause-②: yes (narrowing)).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 02de39cc91be43d2e996f37378dce2360903f42d
Local-runs: none
Scope: packages/services/service-automation/src/connector-pull.ts, packages/services/service-automation/src/connector-input-schema.ts, packages/services/service-automation/src/builtin/connector-nodes.ts, packages/services/service-automation/src/connector-pull.test.ts + packages/services/service-automation/src/index.ts (the connector-pull.js export block, no connector-input-schema export), packages/services/service-automation/package.json (exports: . only), packages/services/service-automation/src/plugin.ts (pullConnectorSource, registry: engine), packages/services/service-automation/src/engine.ts (getConnectorDescriptors), packages/spec/src/data/mapping.zod.ts (connectorSource.input describe), packages/connectors/connector-rest/src/rest-connector.ts (request.inputSchema), packages/connectors/connector-openapi/src/openapi-connector.ts (buildInputSchema), connector-nodes.ts as origin/main holds it, scripts/check-changeset-no-major.mjs and scripts/check-adr-0087-registration.mjs (headers, CATEGORIES); prose faces read for the lane seat's ACCEPT: .changeset/28040-connector-pull-input-schema.md, content/docs/automation/jobs.mdx, packages/spec/docs/SYNC_ARCHITECTURE.md

Reviewed at 2026-10-30T13:55Z, adversarially, on the narrowing face (Clause-②: yes (narrowing) on the PR body and the changeset; reviewFace on the seven paths names no schema or governed hit, so the floor is every path outside the prose faces): the net diff origin/main...origin/claude/project-thread-xrfb3t (merge-base 8cb8c7f4, 7 files, +341/−127; main has not moved on any of the seven since, and git merge-tree composes without conflict), card #28040 (body; triage 6521041814; claim 6522972949 with its 12:56Z amendment; os-dev-report 6524385087), #27995, PR #28035 (merged ec7e15ab) with its records 6520142501 (FAIL) and 6521700736 (PASS), the PR body, file list and comments (claim 6524414202), and the head's check-runs. Read-only: nothing built, run or re-run; the diff of the moved block and the merge-tree are server-free reads.

① Derived judgments

  • Premise holds. On main, connector-pull.ts step 6 builds input from connectorSource.input plus query[watermark.param] and calls handler(input, …) with no comparison against the action's inputSchema; the check fix(service-automation)!: a connector_action call whose input breaks the action's declared inputSchema fails before dispatch, naming each parameter #28035 added lives only in the connector_action executor.
  • One helper, not a copy (triage's graded rule). The block from /** What one judgment found to the end of main's connector-nodes.ts and the same block in src/connector-input-schema.ts diff IDENTICAL (interfaces, MAX_NAMED_VIOLATIONS, JUDGED_TYPES, isPlainRecord, jsonKindOf, matchesType, collectInputSchemaViolations, every comment). findConnectorActionInputSchema is main's descriptor read verbatim (getConnectorDescriptors().find(name).actions.find(key)?.inputSchema) with engine generalised to ConnectorInputSchemaSource; judgeConnectorInput is main's slice(0, MAX) / ; and N more join lifted out of the node. Both doors call the same two functions.
  • The node path is byte-identical. main's message … inputSchema — ${shown.join('; ')} + (more > 0 ? '; and N more' : '') + . Nothing was sent. Fix connectorConfig.input … + (textForScalar ? hint : '') equals the head's … inputSchema — ${verdict.violations} + the same middle + (verdict.textForScalar ? TEXT_FOR_SCALAR_HINT : ''), where verdict.violations is exactly shown.join('; ') + more-suffix and TEXT_FOR_SCALAR_HINT is the two template pieces of main concatenated unchanged; violations.lines.length > 0 ⇔ verdict !== undefined; same code, same success: false, same position (after resolveValueSlotMap, before the try/handler). Its pins are untouched: builtin/connector-input-schema.test.ts and connector-openapi/src/input-schema-dispatch.test.ts are not in the diff.
  • The pull-door refusal is before any upstream call and before any write. It sits after step 4 (the watermark findData read) and step 5 (getMetaItem/getObjectSchema reads and refuseUnknownMappingTargets, a judgment), on the very input object line 407 hands to handler (the watermark key already merged in), before logger.info and handler(...); runImport runs only after the call. refuse throws ConnectorPullError, so nothing is sent and nothing is written. A valid input still pulls: pins 4 (watermark supplies the required parameter, 1 call, the sent object asserted) and 5 (valid input, 1 call, 1 write); every pre-existing pull pin runs with inputSchema: undefined ⇒ the no-schema leg. Pins bite: the three refusal pins assert handler called 0 times and writes empty, the dev's re-measure on main reds exactly them, and the single && false ablation reds exactly them.
  • Error shape is the door's. refuse('VALIDATION_ERROR', 400, 'input_schema_mismatch', 'Mapping "<name>": …') is the same helper, code, status and Mapping "…" prefix the door's other ten input faults use (no_connector_source, connector_not_registered, connector_plugin_origin, connector_provider_unsupported, connector_action_unknown, upsert_key_empty, watermark_field_unmapped, input_query_not_object, …); the new reason is added to the exported ConnectorPullRefusalReason union beside input_query_not_object. The message names each violation by dotted path, says "Nothing was pulled", and prescribes connectorSource.input plus the connectors route. One piece of the message is wrong at this door — ④.
  • What the narrowing refuses that a caller sends today. The rest provider's request schema declares no required and types query/headers only as object with no properties, so a rest pull (the shipped integration pin) is never refused — the watermark key, extra parameters and body are unjudged (additionalProperties is outside the subset). openapi is where it bites: a section with a required parameter is root-required, so a required parameter supplied only by the watermark is absent on the first pull (nothing stored) and refused — stated in the changeset with its fix. No defaults are applied before the judgment at either door (the walker ignores default; a required parameter with a document default is required at both doors alike). Type coercion: the pull door's input is literal JSON from the stored mapping (mapping.zod.ts: "Fixed input passed to the action on every pull"; connector-pull.ts contains no envelope evaluation; plugin.ts passes opts through), so the judgment on text-for-scalar is the node's, and a watermark value is judged as stored (Date → ISO string; other non-JSON objects unjudged). No in-repo mapping declares an openapi pull with a required parameter; the dev's full dogfood battery at this head has one failure, the stale api-action-door-forward pin (test(dogfood): the api-action door's disabled pin reads #27842's sentence, not the ledger table name #28095, fixed on main), not this diff.
  • Clause-② yes (narrowing) is right, and nothing is widened at the entry. src/index.ts exports type ConnectorPullRegistry, which now extends ConnectorInputSchemaSource and so requires getConnectorDescriptors() — the published implementer set narrows (the only implementer in-repo, engine, already has it). ConnectorPullRefusalReason gains a member (a widening for an exhaustive consumer; one arm per line, the narrowing arm is the BREAKING one and dominates). connector-input-schema.ts is not re-exported from index.ts and the package's exports map is . only, so no new name is published; ConnectorInputSchemaSource is reachable through extends in the bundled .d.ts but not addressable — not a published name. PR body, changeset and the claim's 12:56Z amendment agree; triage's no (narrowing) was correctly re-derived from the diff.

② Semver level

@objectstack/service-automation patch — correct. Metadata-protocol: none holds: MappingSchema.connectorSource and ConnectorActionSchema.inputSchema are untouched and registration accepts the same authorable text as before (the refusal is at dispatch), so the LEVEL AXIS (check-changeset-no-major.mjs: a declared none grades every entry patch) gives patch; the title and the changeset's first line carry !, the body carries the BREAKING banner, both declaration lines, and exactly one ADR-0087 marker, not-required (no-migration-prescription), a category in the gate's CATEGORIES and the right one (no key, export, type or code is removed or renamed; nothing stored is rewritten). FROM / TO / who sees it / Fix are present. Check Changeset and Lint · CI, release & engine gates (the changeset and ADR-0087 gates) are green on the head.

③ Boundary flags

  • Author objectstack-fleet[bot]: no login note owed.
  • Dev deviations, accepted: the branch is the card thread's platform-assigned branch (recorded in the claim); Clause-② re-derived to yes from the order's no (the amendment records it; the diff supports it).
  • Open questions: none declared; none owed beyond ④.
  • Out-of-scope findings: the dev's one (api-action-door-forward.dogfood.test.ts:178, red on main since feat(spec,metadata-protocol,rest,objectql,runtime): GET /meta/object serves switched-off packaged actions beside the item, and the disabled-action refusal reads as a plain sentence #27964) is already fixed on main by test(dogfood): the api-action door's disabled pin reads #27842's sentence, not the ledger table name #28095 — no card needed.
  • SHOULD-FIX, changeset "Who sees it" / Fix: a watermark value takes the stored target field's type (Date → ISO string, a number stays a number), so an openapi action declaring the watermark parameter as another type (since: integer against an ISO string, or string against a numeric id) is refused and connectorSource.input cannot fix it; the Fix line should say to declare the watermark parameter in the stored field's type.
  • Check-runs on the head, read at 2026-10-30T13:54Z: 47 runs, 0 failed; 32 completed (17 success — among them Governed Surface Queue Guard, PR-body guards, Check Changeset, Lint · ESLint / CI, release & engine gates / repo & tooling / PM dispatch-gates / scoped lanes, Type Check · source gates, Spec property liveness, Build Docs, Build UI Deps, Test UI, Dogfood Regression Gate — and 15 skipped, the opt-in and paths-filtered lanes); 15 still in_progress (Test Core 1–8, Build Core, Type Check · workspace / consumer gates / debt ledger, Lint · docs & code guards, Live E2E UI, Build & E2E UI, Type Check UI). The queue leg needs them all green; this review adds no verdict of its own on them.
  • Draft PR on the base repository (not a fork); lands through the queue on a PASS record once ④'s must-fix is in.

④ Design judgment

  • connector-input-schema.ts — holds. The move is exact; the module exposes only what a door needs (the schema read, the judgment as one sentence plus the textForScalar flag, the hint constant) and its docblock says each door words its own refusal, which is the right shape for two doors with different prescriptions.
  • builtin/connector-nodes.ts — holds. Same lookup, same judgment, same message, same classification (runtime, routable by a fault edge).
  • connector-pull.ts — MUST-FIX 1. The pull refusal appends TEXT_FOR_SCALAR_HINT ("cast it in a CEL envelope with int(), double() or bool()") whenever text meets a declared integer/number/boolean, but this door evaluates no CEL envelope: connectorSource.input is fixed JSON in the stored mapping and the door spreads it as-is ({ ...source.input }; no resolveValueSlotMap, no envelope read anywhere on the pull path), and the watermark value comes from the stored target field. An author who follows the prescription writes an envelope object into the mapping, and the next pull is refused with 'query.limit' must be integer, got object — or, on an action with no schema, the object is sent to the remote literally. The changeset's own Fix line gives the right prescription for this door ({ query: { limit: 50 } }, not '50'), so the refusal text and the changeset contradict each other; a refusal's prescription must be followable at the door that emits it. Fix: do not append the node's hint at the pull door; word the pull door's own (write the value in the declared type in connectorSource.input; a watermark value is sent as the stored field holds it), and pin in test 3 that the message does not name a CEL cast. The helper's design already allows it (doors word their own refusal); only the blind append is wrong.
  • connector-pull.test.ts — holds, with the pin above owed. The five pins run through the shipped pullConnectorSource with a counting handler and assert the upstream call count and the writes, which is the card's pin; 3 red on main, 3 red under the one-token ablation, the two preservation pins green under it.
  • Narrowing against the published spec on main: it makes ConnectorActionSchema.inputSchema's own declaration real at the second door, taking nothing a consumer could rely on away except a call the declared schema says is wrong; the unjudged subset is the node's, so the two doors cannot disagree without a shared edit.
  • Prose faces, read for the lane seat (not this record's verdict): jobs.mdx — true (ConnectorPullError rejects the pull, so the run is failed and retryPolicy applies, as the sentence already said of the other refusals). SYNC_ARCHITECTURE.md — true (the watermark parameter is included, VALIDATION_ERROR names the parameter, the action is not called); "first" reads as "before the call", and the paragraph's order — binding failures, then this — is the code's order. packages/spec/docs is prose, not a schema face.

Implemented-by: claude/project-thread-xrfb3t
Reviewed-by: session_016WV4czuTWNgKR22fKv7jia (seat subagent)

VERDICT: FAIL

Must-fix:

  1. connector-pull.ts: the input_schema_mismatch refusal appends TEXT_FOR_SCALAR_HINT, a CEL-cast prescription the pull door cannot honour (no envelope is evaluated on connectorSource.input or the watermark value). Replace it with the pull door's own prescription (write the value in the declared type in connectorSource.input; the watermark is sent as stored) and pin in connector-pull.test.ts that the wrong-type refusal names no CEL cast.

… write, not a CEL cast

The pull door evaluates no CEL: connectorSource.input is JSON spread as
written and the watermark is the stored value. Its input_schema_mismatch
refusal now tells the author to write the value as a JSON number or boolean,
and that a watermark parameter is declared with the stored field's type; the
connector_action node keeps its int()/double()/bool() hint unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cxUdTtczoG6TtcncPa
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Contract-review-claim: session_016WV4czuTWNgKR22fKv7jia · head 9cfb92dd5c26c64c570a8d025b926b36e3d79f61 · 2026-10-30T14:01Z

The lane seat re-runs the CONTRACT_REVIEW_TIER review this PR owes at the patch-round head (FAIL 6524588633 at 02de39cc) (Clause-②: yes (narrowing)).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9cfb92dd5c26c64c570a8d025b926b36e3d79f61
Local-runs: none
Scope: packages/services/service-automation/src/connector-pull.ts, packages/services/service-automation/src/connector-input-schema.ts, packages/services/service-automation/src/builtin/connector-nodes.ts, packages/services/service-automation/src/connector-pull.test.ts + packages/services/service-automation/src/index.ts (the connector-pull.js export block), packages/services/service-automation/src/plugin.ts (pullConnectorSource), packages/services/service-automation/src/engine.ts (getConnectorDescriptors), packages/spec/src/data/mapping.zod.ts (connectorSource.input describe), packages/connectors/connector-rest/src/rest-connector.ts, packages/connectors/connector-openapi/src/openapi-connector.ts (buildInputSchema), connector-nodes.ts as origin/main holds it; prose faces read for the lane seat's ACCEPT: .changeset/28040-connector-pull-input-schema.md, content/docs/automation/jobs.mdx, packages/spec/docs/SYNC_ARCHITECTURE.md

Delta review at 2026-10-30T14:02Z, adversarially, of head 9cfb92dd against the FAIL record 6524588633 on 02de39cc; this record supersedes it. Read: git diff 02de39cc..9cfb92dd (one commit, 3 files, +14/−5: the changeset, connector-pull.ts, connector-pull.test.ts), the net diff origin/main...9cfb92dd (merge-base 8cb8c7f4, 7 files, +350/−127; main at 93242324 has not moved on any of the seven, and git merge-tree composes without conflict), the PR body, file list and comments, and the head's check-runs. Read-only: nothing built, run or re-run. Every judgment of 6524588633 not named here stands unchanged at this head: premise, the single shared helper, the pull refusal's placement before any upstream call or write, the error shape, what the narrowing refuses, Clause-② yes (narrowing), the semver level and markers, the prose faces.

① Derived judgments

  • The delta is the patch commit and nothing else. The three files it touches are the changeset's TO and Fix paragraphs, the pull door's hint and comment, and one pin. connector-input-schema.ts and builtin/connector-nodes.ts are byte-for-byte what 02de39cc held; the moved block still diffs IDENTICAL against main's connector-nodes.ts tail, so the node's message, order and set of violations are unchanged and its pins (builtin/connector-input-schema.test.ts, connector-openapi/src/input-schema-dispatch.test.ts) are still not in the diff. TEXT_FOR_SCALAR_HINT stays exported from the helper for the node, which still appends it; the pull door no longer imports it.
  • Must-fix 1 — resolved. The pull refusal now appends its own sentence when verdict.textForScalar is set: "A text value for an integer, number or boolean parameter is not converted: write it in connectorSource.input as a JSON number or boolean (50, not '50'); a watermark parameter carries the stored target field's value, so its declared type must be that field's." Each clause is true against the code: nothing is coerced (the helper's walker is unchanged); connectorSource.input is fixed JSON spread as written ({ ...source.input }, no envelope evaluation on the pull path, plugin.ts passes opts through); the watermark key is stored instanceof Date ? stored.toISOString() : stored, so the parameter is sent as the stored target field's value and the declared type must be that field's. The prescription names the thing the author can change at this door (connectorSource.input, or the action's declared type for the watermark parameter) and no CEL cast. The comment at the hunk states why the node's hint cannot apply here. Nothing else in the refusal moved: same refuse('VALIDATION_ERROR', 400, 'input_schema_mismatch', 'Mapping "…": …'), same violations sentence, same "Nothing was pulled" and route pointer.
  • Pinned. Test 3 (query.limit: '50' against integer) now asserts the message contains as a JSON number or boolean and not.toMatch(/CEL|int\(\)|double\(\)|bool\(\)/); the regex is case-sensitive and parenthesised, so integer and (50, not '50') in the message do not match it, and the node's hint would. The pin would be red on 02de39cc (the appended text was the CEL hint) and is green only on a hint that names no cast.
  • Should-fix (watermark type) — resolved in the changeset. The Fix paragraph now says the watermark parameter is sent as the value stored in its target field, so the action must declare it with the stored field's type (text or datetime ⇒ string, the ISO form a pull sends for a date), and must not be required unless connectorSource.input gives it a first value. True: a Date goes out as an ISO string, any other stored value as-is. The TO paragraph's new clause ("a text value where a number or boolean is declared adds a hint to write it as that JSON type; no CEL runs on this door, so there is no cast to suggest") matches the code.

② Semver level

@objectstack/service-automation patch — unchanged, correct. The delta edits prose inside the changeset body only; the frontmatter, !, BREAKING banner, both declaration lines (Clause-②: yes (narrowing), Metadata-protocol: none) and the single ADR-0087 marker not-required (no-migration-prescription) are as before. Check Changeset and the lint lane carrying the ADR-0087 gate are re-running on this head (③).

③ Boundary flags

  • Author objectstack-fleet[bot]: no login note owed.
  • Dev deviations, accepted: as in 6524588633 (platform-assigned branch; Clause-② re-derived to yes).
  • Open questions: none.
  • Out-of-scope findings: the stale api-action-door-forward dogfood pin is fixed on main by test(dogfood): the api-action door's disabled pin reads #27842's sentence, not the ledger table name #28095; no card.
  • SHOULD-FIX, seat's body patch (not holding the verdict): the PR body's Verification section still reads "All local readings are at head 02de39cc91", the ablation names blob 6b358231 (now 89f14ba0), and Tests item 3 does not mention the hint pin; the body should name this head as the one measured, or the os-dev-report for this head should.
  • Check-runs on the head, read at 2026-10-30T14:02Z: 23 runs so far, 0 failed; 9 completed success (Governed Surface Queue Guard, PR-body guards, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Spec property liveness, Auto Label), 3 skipped (opt-in lanes), 14 in_progress (Check Changeset, every Lint lane, Type Check · workspace / source gates / consumer gates / debt ledger); the Build / Test Core / UI runs had not yet started. The queue leg needs them all green; this review adds no verdict of its own on them. On the previous head the same set finished 0 failed, and the delta touches no path that changes which lanes run.
  • The narrowing arm's dogfood battery is on record for 02de39cc (report 6524385087: 357 files passed, 1 failed = the test(dogfood): the api-action door's disabled pin reads #27842's sentence, not the ledger table name #28095 stale pin, 3 skipped); the delta changes one refusal string and one changeset paragraph, no path the battery exercises differently.

④ Design judgment

  • connector-pull.ts — holds. The door now words its own refusal, which is the shape the helper's docblock prescribes ("each door words its own refusal around the sentence judgeConnectorInput returns"); the shared judgment is untouched, so the two doors still cannot disagree on what is refused, only on what each tells its author to do, which is right because the doors' inputs come from different places (an evaluated value-slot map versus stored JSON plus a stored watermark).
  • connector-pull.test.ts — holds. The hint is pinned positively and negatively on the one wrong-type pin, through the shipped door.
  • connector-input-schema.ts, builtin/connector-nodes.ts — hold, unchanged.
  • Narrowing against the published spec on main: unchanged from 6524588633 — it makes ConnectorActionSchema.inputSchema's declaration real at the pull door, and now with a prescription the pull author can follow.
  • Prose faces, read for the lane seat: the changeset's TO and Fix sentences are true against the diff (above); jobs.mdx and SYNC_ARCHITECTURE.md are unchanged since the previous record and still true.

Implemented-by: claude/project-thread-xrfb3t
Reviewed-by: session_016WV4czuTWNgKR22fKv7jia (seat subagent)

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Seat ACCEPT · PR #28140 · head 9cfb92dd5c26c64c570a8d025b926b36e3d79f61 · 2026-10-30T15:15Z

维护者速读

席位核对

  • Shape: draft → main, Fixes #28040. 7 files (+350/−127): connector-input-schema.ts (new, the moved checker), builtin/connector-nodes.ts (imports it), connector-pull.ts + test, one changeset, content/docs/automation/jobs.mdx, packages/spec/docs/SYNC_ARCHITECTURE.md (prose). check-governed-merges --pr 28140: NOT governed.
  • Premise: re-measured on main by the thread: a pull whose input misses a required parameter reached the handler.
  • File surface: within the claim as amended (two docs files added in the amendment).
  • Declarations: Clause-②: yes (narrowing) (narrows the pull's dispatch; the exported ConnectorPullRegistry now needs getConnectorDescriptors()), Metadata-protocol: none, fix(service-automation)!:, BREAKING banner, adr-0087: not-required (no-migration-prescription); @objectstack/service-automation patch. PR body and changeset agree.
  • Contract review: CONTRACT_REVIEW_TIER. FAIL 6524588633 at 02de39cc (one must-fix: the pull door's refusal carried the node's CEL-cast hint, which that door cannot follow), fixed in one commit; PASS at this head, 6524722687.
  • Dogfood battery (narrowing arm): 2882 passed / 1 failed / 20 skipped at this head. The one red is api-action-door-forward.dogfood.test.ts's ACTION_DISABLED pin, stale on this branch's base 8cb8c7f4 and already fixed on main by test(dogfood): the api-action door's disabled pin reads #27842's sentence, not the ledger table name #28095; the queue tests the merge with main.
  • Gate check-runs on this head: 50 total, 41 success, 9 skipped by the path filter, 0 failed. mergeable_state: clean.
  • Review request: the card thread requested os-elon-musk before the seat's instruction reached it; Tier S needs no human review and it is not a merge condition.
  • Landing: Tier S, under the maintainer's landing grant for this term (2026-10-29 22:05Z, seat thread). Ready + auto-merge through the relay.

ACCEPT

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 15:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit c48cd2c Oct 30, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-xrfb3t branch October 30, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant