You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
connector-openapi: query parameters are sent through String(value), so an object or array value (style: deepObject, explode) goes out as [object Object] or a comma list instead of the encoding the document declares #28504
Filing gate: ① a product defect with a named location, finding class (a) (reproducible). Found by the os-dev on #28276 (the Stripe app, PR #28503) and re-read by this seat. Child of #27293 (E1). ⛔ Not a claim.
reach: public door — POST /api/v1/automation/:flow/trigger running a connector_action on a declarative openapi instance. Named real producer: Stripe's spec3.json (stripe/openapi, MIT), whose list operations declare expand, created and due_date as query parameters with style: deepObject (GetInvoices, GetCharges, GetCustomers, GetPaymentIntents). Measured by the dev (one-off bootStack probe, fake fetchImpl recording the URL): created: { gte: 1700000000 } went out as created=%5Bobject+Object%5D, expand: ['a', 'b'] as expand=a%2Cb, and the run answered 200. Stripe's own answer is NOT MEASURED (no key).
The defect
packages/connectors/connector-openapi/src/openapi-connector.ts:563 (read at origin/main 2026-11-01T03:40Z): url.searchParams.set(key, String(value)); the path builder (:592) does the same with encodeURIComponent(String(value)). The parameter's style / explode (OpenAPI 3.x Parameter Object) are never read, while the action's input schema advertises the object/array shape — so an author or an AI that fills the advertised input sends a malformed request, and nothing says so.
Serialize query (and path) parameters by their declared style and explode (at least form explode true/false for arrays, deepObject for objects; the OpenAPI 3.x defaults when absent), or refuse at materialization an operation whose parameter uses a style the connector does not serialize — never String() a non-scalar. Tests: the three Stripe shapes above as fixtures, plus a scalar control.
Business reason
Every list call on Stripe (and most large vendor APIs) filters by date ranges or expands related objects through these parameters; today the request is silently wrong.
Reader
The triage seat grades and routes it (domain:server, connector-openapi); the epic PM of #27293 dispatches.
Dedupe
The 500 most recent issues and PRs (read 2026-11-01T03:42Z), titles grepped for deepObject|explode|query string|object Object: 0 hits; control openapi on the same set: 7 hits.
Read fresh, and measured by the dev with a probe that records the URL. An object or array query value goes out as [object Object] or as a comma list, and the run answers 200. The request is silently wrong, which makes this a defect, so bug is added. It is graded p2, in step with its E1 siblings. Stripe's own answer is not measured.
The fix is the card's: serialize by style / explode, with the OpenAPI 3.x defaults when they are absent, or refuse the operation loudly at materialization. A non-scalar value is never passed through String(). The tests are the three Stripe fixtures plus a scalar control. The lane is packages/connectors/connector-openapi.
Claim: PM loop round 4
Session: session_017xRBcVBKAFk1ntXSjvPt7U
Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-28504-openapi-param-style
Worktree: objectstack-issue-28504
Domain: domain:server
Seat: epic:#27293#1
File surface: packages/connectors/connector-openapi/src/** (the query and path builders and the operation materializer that reads each parameter's style / explode) and its tests and fixtures; content/docs/automation/connectors.mdx if it states how parameters are sent; a @objectstack/connector-openapi changeset (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus (TIER_DEFAULT — dispatch-gates --tier --repo objectstack-ai/objectstack this fire: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: connector-openapi's URL builder (openapi-connector.ts:563, path at :592) passes every parameter value through String() and never reads the Parameter Object's style / explode | no other path serializes connector_action query input; the action's input schema advertises the object/array shape | any author or AI that fills a deepObject or array parameter on a declarative openapi instance; Stripe's list operations (PR #28503) are the first on the shelf
Thread-read: 6553109544
Premise: holds (read at origin/main23d1303d 2026-11-01T04:06Z: url.searchParams.set(key, String(value)) at :563, encodeURIComponent(String(value)) at :592; style / explode 0 reads in that file)
Serial constraints cleared: none — none of the 60 most recent open PRs touches packages/connectors/connector-openapi/src/** (files read 2026-11-01T04:06Z).
{
"issue": 28504,
"status": "done",
"branch": "claude/issue-28504-openapi-param-style",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28537",
"session": "session_017xRBcVBKAFk1ntXSjvPt7U",
"premise_still_valid": true,
"summary": "Premise held at 23d1303d: query values went through stringifyValues/String() (openapi-connector.ts:326/601, then :563) and path values through encodeURIComponent(String()) (:592); headers took the same stringifyValues path; cookie parameters are never sent (left out of the input schema). style/explode survive into the operation record: the normalizer copies parameter objects whole, and the packaged Stripe document carries style deepObject + explode true on created/due_date/expand. New src/parameter-serialization.ts encodes every path/query/header value by its declared style/explode or the OAS 3.x location default (query form+explode; path/header simple), implementing form, spaceDelimited, pipeDelimited, deepObject, simple, label, matrix; deepObject on an array brackets each index (expand[0]=a), the style's own rule applied to the array's keys. A style OAS 3.x does not define for the location (or a non-boolean explode) refuses the operation at materialization naming operation+parameter (an include that omits it clears it); a value shape the style cannot carry, or a non-scalar for a content-typed parameter, fails the dispatch before fetch. Scalars are byte-identical to before (ablation: both scalar controls green on the old code). Docs: connectors.mdx states how parameters are sent plus two failure-table rows; the connector-stripe README Limits bullet that told authors to leave expand/created out was made false by this change and is corrected (outside the claim's file surface, see deviations). Changeset: @objectstack/connector-openapi patch.",
"tests": "HEAD 73e2a2ec. src/parameter-style.test.ts 17/17 (three Stripe shapes with GetInvoices' five parameter objects copied verbatim; one-off canonical-JSON compare against packages/apps/connector-stripe/openapi/stripe.openapi.json: 'packaged 5 fixture 5 equal true'; query and path scalar controls; style table; api-key query merge; both refusals, dispatch refusal asserts ParameterValueError + 0 fetch calls). pnpm --filter @objectstack/connector-openapi test: Test Files 9 passed (9), Tests 109 passed (109). typecheck exit 0, check:test-typecheck OK (1 file / 5 errors / 1 signature, pre-existing ledger unchanged). pnpm --filter @objectstack/connector-app-stripe test: objectstack validate passed, Test Files 2 passed (2), Tests 16 passed (16). Ablation (fix committed first; base openapi-connector.ts written over the file, mutant 'String(value)' count 2 / 'serializeQuery' count 0, trap restore to HEAD, git hash-object re-matched HEAD blob, git diff HEAD empty): 15 failed | 2 passed (17), the 2 green being the scalar controls; old outputs created=%5Bobject+Object%5D, expand=data.customer%2Cdata.subscription, due_date=%5Bobject+Object%5D. No dist rebuild leg needed: the test imports src by relative path. Public-door one-off probe (not committed, deleted): packaged Stripe app via bootStack, POST /automation/:flow/trigger on a GetInvoices flow answered 200, fake Stripe received https://api.stripe.com/v1/invoices?created%5Bgte%5D=1700000000&due_date%5Bgte%5D=1700000000&due_date%5Blt%5D=1702592000&expand%5B0%5D=data.customer&expand%5B1%5D=data.subscription&limit=3. Stripe's own answer NOT MEASURED (no key).",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 commands for the 6 changed paths at 73e2a2ec (after merging origin/main eeec88d1); all 88 run, exit codes captured before any pipe: 88 exit 0. First pass at 62f84496 had 2 exit 3 (check:skill-examples needed client-react dist, check:dual-build-cjs-loads needed built dists); both re-ran exit 0 after building the closures. --ran reconciliation: '88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. check-nul-bytes exit 0; control-byte self-scan 0 hits. No export renamed or removed (stringifyValues was module-private), so check-platform-checklist --changed-from was not owed. CI check-runs on 73e2a2ec at report time: Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI, release & engine gates; ESLint; PM dispatch-gates; docs & code guards; repo & tooling gates; scoped query-options erasure; scoped slot lookup & corpus; scoped tooling self-tests), PR-body guards, Spec property liveness, Type Check (consumer gates; debt ledger; source gates; workspace), filter = in_progress; 18.x upgrade harness, Packed-tarball smoke, Schedule gate = skipped. Test Core / Build Core not yet listed. test:repo not run locally (diff touches no repo-project test), declared to CI.",
"dogfood_battery": "n/a — no `!` in the PR title and Clause-② is `no` with no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 6284 bytes identical) and this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/28504/comments); plus git push of the branch (not a REST write)",
"open_questions": [
{
"question": "deepObject on an array is undefined in OAS 3.x; Stripe declares it on expand. Which wire form?",
"options": [
"A expand[0]=a&expand[1]=b (each index in brackets: deepObject's property-in-brackets rule applied to the array's keys; also unambiguous for nested arrays of objects)",
"B expand[]=a&expand[]=b (the bracket-list convention Stripe's curl docs show)"
],
"recommendation": "A, shipped in this PR, because it is derived from the declared style rather than a vendor convention; non-blocking — Stripe's acceptance of either is NOT MEASURED here"
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — cookie parameters are left out of the action's input schema and never sent (pre-existing, unchanged; no public-door reach measured)",
"carrier: none · noted, not filed — Parameter Object allowReserved is not read; reserved characters stay percent-encoded as before"
],
"deviations": "One file outside the claim's file surface: packages/apps/connector-stripe/README.md (private package) — its Limits bullet said expand/created[gte] are not sent in Stripe's form so leave them out; this change made that false, so the bullet now says how they are sent and that Stripe's answer is not measured. Merged origin/main once (eeec88d1) before opening the PR, which brought PR #28503's Stripe app onto the branch base. Commits carry the model-free trailer pair, not the harness reminder's model-named Co-Authored-By.",
"files_changed": [
".changeset/connector-openapi-parameter-style.md",
"content/docs/automation/connectors.mdx",
"packages/apps/connector-stripe/README.md",
"packages/connectors/connector-openapi/src/openapi-connector.ts",
"packages/connectors/connector-openapi/src/parameter-serialization.ts",
"packages/connectors/connector-openapi/src/parameter-style.test.ts"
]
}
MERGED → PR #28537 through the merge queue (merge commit a52191f69) · 2026-11-01T06:00Z. Closed completed by its Fixes #28504. pm:dispatched is removed in this act; pm:epic is kept. Every path, query and header parameter of a declarative openapi instance is now sent by its declared style / explode, so the Stripe app's created, due_date and expand filters leave in the form its document declares. Whether Stripe itself accepts them is NOT MEASURED (no key).
Activity
objectstack-fleet commentedon Nov 1, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 审批与自动化 | none | P②
Triage: p2 ·
bug·domain:server·area:automation·pm:epickept · 2026-11-01T03:55Z⛔ Not a claim.
[object Object]or as a comma list, and the run answers 200. The request is silently wrong, which makes this a defect, sobugis added. It is graded p2, in step with its E1 siblings. Stripe's own answer is not measured.style/explode, with the OpenAPI 3.x defaults when they are absent, or refuse the operation loudly at materialization. A non-scalar value is never passed throughString(). The tests are the three Stripe fixtures plus a scalar control. The lane ispackages/connectors/connector-openapi.pm:epicstays. The epic PM of [Epic] Marketplace and connector auto-shelf — E1 platform prerequisites (openapi/mcp hardening, outbound OAuth, request signing), E2 administrator credential hand-off, E3 author recipes and handbook, E4 the first shelf of official connector apps #27293 dispatches it.Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Claim: PM loop round 4
Session:
session_017xRBcVBKAFk1ntXSjvPt7UAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-28504-openapi-param-styleWorktree:
objectstack-issue-28504Domain:
domain:serverSeat:
epic:#27293#1File surface:
packages/connectors/connector-openapi/src/**(the query and path builders and the operation materializer that reads each parameter'sstyle/explode) and its tests and fixtures;content/docs/automation/connectors.mdxif it states how parameters are sent; a@objectstack/connector-openapichangeset (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opus(TIER_DEFAULT —dispatch-gates --tier --repo objectstack-ai/objectstackthis fire: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
connector-openapi's URL builder (openapi-connector.ts:563, path at :592) passes every parameter value through String() and never reads the Parameter Object's style / explode | no other path serializes connector_action query input; the action's input schema advertises the object/array shape | any author or AI that fills a deepObject or array parameter on a declarative openapi instance; Stripe's list operations (PR #28503) are the first on the shelfThread-read: 6553109544
Premise: holds (read at
origin/main23d1303d2026-11-01T04:06Z:url.searchParams.set(key, String(value))at:563,encodeURIComponent(String(value))at:592;style/explode0 reads in that file)Serial constraints cleared:
none— none of the 60 most recent open PRs touchespackages/connectors/connector-openapi/src/**(files read 2026-11-01T04:06Z).objectstack-fleet commentedon Nov 1, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
os-dev-report
{ "issue": 28504, "status": "done", "branch": "claude/issue-28504-openapi-param-style", "pr": "https://github.com/objectstack-ai/objectstack/pull/28537", "session": "session_017xRBcVBKAFk1ntXSjvPt7U", "premise_still_valid": true, "summary": "Premise held at 23d1303d: query values went through stringifyValues/String() (openapi-connector.ts:326/601, then :563) and path values through encodeURIComponent(String()) (:592); headers took the same stringifyValues path; cookie parameters are never sent (left out of the input schema). style/explode survive into the operation record: the normalizer copies parameter objects whole, and the packaged Stripe document carries style deepObject + explode true on created/due_date/expand. New src/parameter-serialization.ts encodes every path/query/header value by its declared style/explode or the OAS 3.x location default (query form+explode; path/header simple), implementing form, spaceDelimited, pipeDelimited, deepObject, simple, label, matrix; deepObject on an array brackets each index (expand[0]=a), the style's own rule applied to the array's keys. A style OAS 3.x does not define for the location (or a non-boolean explode) refuses the operation at materialization naming operation+parameter (an include that omits it clears it); a value shape the style cannot carry, or a non-scalar for a content-typed parameter, fails the dispatch before fetch. Scalars are byte-identical to before (ablation: both scalar controls green on the old code). Docs: connectors.mdx states how parameters are sent plus two failure-table rows; the connector-stripe README Limits bullet that told authors to leave expand/created out was made false by this change and is corrected (outside the claim's file surface, see deviations). Changeset: @objectstack/connector-openapi patch.", "tests": "HEAD 73e2a2ec. src/parameter-style.test.ts 17/17 (three Stripe shapes with GetInvoices' five parameter objects copied verbatim; one-off canonical-JSON compare against packages/apps/connector-stripe/openapi/stripe.openapi.json: 'packaged 5 fixture 5 equal true'; query and path scalar controls; style table; api-key query merge; both refusals, dispatch refusal asserts ParameterValueError + 0 fetch calls). pnpm --filter @objectstack/connector-openapi test: Test Files 9 passed (9), Tests 109 passed (109). typecheck exit 0, check:test-typecheck OK (1 file / 5 errors / 1 signature, pre-existing ledger unchanged). pnpm --filter @objectstack/connector-app-stripe test: objectstack validate passed, Test Files 2 passed (2), Tests 16 passed (16). Ablation (fix committed first; base openapi-connector.ts written over the file, mutant 'String(value)' count 2 / 'serializeQuery' count 0, trap restore to HEAD, git hash-object re-matched HEAD blob, git diff HEAD empty): 15 failed | 2 passed (17), the 2 green being the scalar controls; old outputs created=%5Bobject+Object%5D, expand=data.customer%2Cdata.subscription, due_date=%5Bobject+Object%5D. No dist rebuild leg needed: the test imports src by relative path. Public-door one-off probe (not committed, deleted): packaged Stripe app via bootStack, POST /automation/:flow/trigger on a GetInvoices flow answered 200, fake Stripe received https://api.stripe.com/v1/invoices?created%5Bgte%5D=1700000000&due_date%5Bgte%5D=1700000000&due_date%5Blt%5D=1702592000&expand%5B0%5D=data.customer&expand%5B1%5D=data.subscription&limit=3. Stripe's own answer NOT MEASURED (no key).", "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 commands for the 6 changed paths at 73e2a2ec (after merging origin/main eeec88d1); all 88 run, exit codes captured before any pipe: 88 exit 0. First pass at 62f84496 had 2 exit 3 (check:skill-examples needed client-react dist, check:dual-build-cjs-loads needed built dists); both re-ran exit 0 after building the closures. --ran reconciliation: '88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. check-nul-bytes exit 0; control-byte self-scan 0 hits. No export renamed or removed (stringifyValues was module-private), so check-platform-checklist --changed-from was not owed. CI check-runs on 73e2a2ec at report time: Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI, release & engine gates; ESLint; PM dispatch-gates; docs & code guards; repo & tooling gates; scoped query-options erasure; scoped slot lookup & corpus; scoped tooling self-tests), PR-body guards, Spec property liveness, Type Check (consumer gates; debt ledger; source gates; workspace), filter = in_progress; 18.x upgrade harness, Packed-tarball smoke, Schedule gate = skipped. Test Core / Build Core not yet listed. test:repo not run locally (diff touches no repo-project test), declared to CI.", "dogfood_battery": "n/a — no `!` in the PR title and Clause-② is `no` with no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no labels, no assignees; read-back 6284 bytes identical) and this os-dev-report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/28504/comments); plus git push of the branch (not a REST write)", "open_questions": [ { "question": "deepObject on an array is undefined in OAS 3.x; Stripe declares it on expand. Which wire form?", "options": [ "A expand[0]=a&expand[1]=b (each index in brackets: deepObject's property-in-brackets rule applied to the array's keys; also unambiguous for nested arrays of objects)", "B expand[]=a&expand[]=b (the bracket-list convention Stripe's curl docs show)" ], "recommendation": "A, shipped in this PR, because it is derived from the declared style rather than a vendor convention; non-blocking — Stripe's acceptance of either is NOT MEASURED here" } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — cookie parameters are left out of the action's input schema and never sent (pre-existing, unchanged; no public-door reach measured)", "carrier: none · noted, not filed — Parameter Object allowReserved is not read; reserved characters stay percent-encoded as before" ], "deviations": "One file outside the claim's file surface: packages/apps/connector-stripe/README.md (private package) — its Limits bullet said expand/created[gte] are not sent in Stripe's form so leave them out; this change made that false, so the bullet now says how they are sent and that Stripe's answer is not measured. Merged origin/main once (eeec88d1) before opening the PR, which brought PR #28503's Stripe app onto the branch base. Commits carry the model-free trailer pair, not the harness reminder's model-named Co-Authored-By.", "files_changed": [ ".changeset/connector-openapi-parameter-style.md", "content/docs/automation/connectors.mdx", "packages/apps/connector-stripe/README.md", "packages/connectors/connector-openapi/src/openapi-connector.ts", "packages/connectors/connector-openapi/src/parameter-serialization.ts", "packages/connectors/connector-openapi/src/parameter-style.test.ts" ] }objectstack-fleet commentedon Nov 1, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
MERGED → PR #28537 through the merge queue (merge commit
a52191f69) · 2026-11-01T06:00Z. Closedcompletedby itsFixes #28504.pm:dispatchedis removed in this act;pm:epicis kept. Every path, query and header parameter of a declarativeopenapiinstance is now sent by its declaredstyle/explode, so the Stripe app'screated,due_dateandexpandfilters leave in the form its document declares. Whether Stripe itself accepts them is NOT MEASURED (no key).