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
spec(contracts): IHttpRequest.body's TSDoc says "parsed — JSON or form data when applicable", but since PR #24005 reading it on plugin-hono-server throws a 400 refusal for a body the server cannot parse #24043
Filing gate ①: a published contract sentence that a named real producer contradicts. reach: the producer is @objectstack/plugin-hono-server's HonoHttpServer, the transport os dev and os serve run. Since PR #24005 (merged 75ced061b8), reading req.body throws RequestBodyRefusal (400 INVALID_REQUEST) for a declared-JSON body that does not parse, or a non-empty body in a media type it does not read. This is pinned in packages/plugins/plugin-hono-server/src/request-body-refusal.test.ts.
Filed by the domain:cli seat 2 (seat post #22648) · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT, from PR #24005's contract review (6222965551, residual 6). ⛔ Not a claim; the grade and lane are triage's.
Reader who acts: the domain:spec seat, for packages/spec/src/contracts/http-server.ts.
/** Request body (parsed — JSON or form data when applicable) */
body?: any;
A plugin author reading this contract learns that body is a parsed value. On the standard transport, reading it can now throw, and the transport answers that throw itself with a 400, whatever the handler does with it. A handler that wraps the read in its own try/catch and writes anyway would write after a 400. PR #24005's review found no such handler in the repo; the contract should say so for the ones that come.
Done when
IHttpRequest.body's TSDoc states what it is:
the parsed JSON or form body;
{} when there is no body;
that reading it refuses (the transport answers 400 INVALID_REQUEST) when the body cannot be parsed or is in a media type the server does not read;
that a handler which needs the raw bytes reads rawBody().
TSDoc only; no type change.
Dedupe
MCP search_issues on this repository, closed included: IHttpRequest body TSDoc parsed JSON or form data stale request body getter can throw http-server contract gave 0 hits.
Dedupe words: IHttpRequest body TSDoc; http-server contract body parsed; request body getter throws.
Claim: PM loop round 19 (batch 2) · 2026-10-16T22:23Z · IHttpRequest.body's TSDoc in packages/spec/src/contracts/http-server.ts states what a reader can receive and that reading it may refuse, as PR #24005 made the standard transport do; TSDoc only, no type change. One PR, Fixes #24043.
Session: session_01HjR8RUwTPQ3TgbfKihmxp9 (the dispatching seat; the dev runs as a cloud session the seat post names)
Account: os-elon-musk (the seat's linked user as GET /user answers it; this card's assignee)
Branch: claude/issue-24043-http-request-body-tsdoc
Worktree: objectstack-issue-24043 (in the cloud session's container)
Domain: domain:spec
Seat: domain:spec#1 (seat post #6017)
File surface (paths on origin/maine246f06119 or later; stop on a breach and explain it in the report):
packages/spec/src/contracts/http-server.ts: the IHttpRequest.body TSDoc (:23 today), and the rawBody TSDoc (:31-:36) only if it then contradicts the new body text.
One prose-only patch changeset for @objectstack/spec, since the TSDoc ships in the package's .d.ts.
⛔ No type change, no other member, no implementation. ⛔ Not plugin-hono-server, qa/http-conformance or rest.
Container & model: S, mode:cloud (the maintainer's word in this session's chat: 「batch 调到 5, 可以开云卡」, now 「并发降到2」), model: default tier. Review: a CONTRACT_REVIEW_TIER contract review before the queue (non-test prose under packages/spec/src/**).
Clause-②: no
Responsibility: n/a — a published contract sentence PR fix(plugin-hono-server,hono,cloud-connection)!: refuse an unparseable request body with 400 INVALID_REQUEST instead of reading it as {} #24005 made incomplete, filed by domain:cli seat 2 from that PR's contract review (6222965551, residual 6)
Thread-read: 6225531438
Ruling-ref: n/a (the card's done-when; triage 6225531438)
Serial constraints cleared:
{
"issue": 24043,
"status": "done",
"branch": "claude/issue-24043-http-request-body-tsdoc",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24068",
"session": "session_01KNNm3SRPyw288E5DT4qVGx",
"premise_still_valid": true,
"summary": "IHttpRequest.body's TSDoc (packages/spec/src/contracts/http-server.ts) now covers each done-when clause. It says the body is the parsed JSON document or form fields, {} when there is no body, and that reading it may refuse. The standard transport (plugin-hono-server) throws on a body it cannot parse or a non-empty body in a media type it does not read, and answers 400 INVALID_REQUEST itself, replacing what the handler wrote, so a handler must not catch the refusal and carry on. A handler that needs bytes reads rawBody(). The text is phrased as what an implementation MAY do, not as every producer's behaviour. TSDoc only: no type change; rawBody's TSDoc unchanged (it does not contradict the new text); one prose-only patch changeset for @objectstack/spec. Draft PR #24068 is open, assigned to os-elon-musk, and the body read back byte-identical (6684 bytes).",
"tests": "At head 2a55ea38 (same tree as 8026677d, where the gates ran; only the trailer was reworded). pnpm --filter @objectstack/spec build, under os-verify-lock: VERDICT command-exit 0. After the build, grep -rl 'must not catch the refusal and carry on' dist matches dist/contracts/index.d.ts and index.d.mts; the positive control 'Raw body accessor for binary uploads' matches the same two files; the old sentence 'parsed — JSON or form data when applicable' matches 0 files in dist; dist is in files[]. pnpm --filter @objectstack/spec typecheck: VERDICT command-exit 0. No behaviour changed, so no test was added and no ablation was run (TSDoc only).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 78 commands. I ran all 78, each exit code captured before any pipe, plus 5 roster families whose roster sits under a directory of this diff (check-changeset-fixed, spec check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity): 79 exit 0, 4 exit 3. The four exit-3 results are PREREQUISITE NOT MET and are NOT MEASURED, not failures: check-plugin-teardown-shape --self-test (the shallow clone cannot reach pinned control 621a4876), lint check:doc-formula-expressions (@objectstack/formula not built), check:dual-build-cjs-loads and check:lean-entry-closure (workspace dist not built). None of them reads http-server.ts or a changeset. --ran reconciliation with exit codes: '78 derived famil(ies) accounted for — 74 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3)', exit 0. Also: spec check:generated exit 0; ESLint --no-inline-config over both changed files exit 0 (the .md file is ignored by the config, a warning only). CI check-runs on PR #24068: in_progress at report time; per the contract I did not wait for them.",
"mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments), mcp__github__pull_request_read (get); all reads, no write tool",
"api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each a POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft forced) + POST /repos/objectstack-ai/objectstack/issues/24068/assignees; (2) this report = POST /repos/objectstack-ai/objectstack/issues/24043/comments. git push x3 (an empty-branch probe, one push refused by the pre-push trailer hook, then the landing push); a push is not a REST write. No label-write: a changeset is present, so skip-changeset does not apply.",
"line_budget": 30,
"files_changed": ["packages/spec/src/contracts/http-server.ts", ".changeset/24043-http-request-body-tsdoc.md"],
"deviations": [
"The first commit carried the harness's model-named Co-Authored-By trailer. The pre-push check:commit-card-trailers hook refused it, and I amended the unpublished tip to the model-free pair 'Claude' with noreply@anthropic.com (tree unchanged, verified with an empty git diff 8026677d HEAD). Nothing published was rewritten.",
"Worktree: the cloud container's primary checkout already had the branch checked out, so I detached it and attached the existing branch to ../objectstack-issue-24043 instead of using worktree add -b from origin/main. Same base, e246f06119.",
"Local narrowing: four derived families exited 3 (PREREQUISITE NOT MET: a shallow clone, or sibling packages not built) and are NOT MEASURED locally. CI runs them. spec pnpm test was not run locally: the diff is TSDoc plus a changeset with no behaviour change, so it is declared to CI's Test Core.",
"I did not call subscribe_pr_activity. os-dev.md says the dispatch contract overrides the platform's PR-subscription posture."
],
"open_questions": [
{
"question": "Done-when clause '{} when there is no body' holds on the standard transport (request-body.ts last arm; pins 'a request with no body is {}' and the over-the-wire CONTROL), and also on qa/http-conformance. The test builder packages/rest/src/http-request-test-builder.ts leaves body undefined unless a test passes one, and the member stays optional (body?). Should the contract say '{}' unconditionally?",
"options": ["A: keep the sentence as written ('{} when the request carries no body'), the member optional, and test builders free to omit it", "B: a follow-up card makes body required and the builder default to {}, which is a type change outside this card"],
"recommendation": "A, because the card forbids a type change, and the builder's own header records a deliberate choice that absent means 'the fixture supplied none'. Contract-first argues for B only if a real handler is shown to break on undefined; none was measured."
},
{
"question": "The handler consequence. The card says a handler that catches and writes anyway 'would write after a 400'. Measured in adapter.ts runHandler: if refusedBodyRead is set, the adapter returns the declared 400 envelope in place of whatever the handler wrote (pinned by the query door, which catches the read and answers its own 500, yet the wire shows 400). So the response is discarded, and only the handler's side effects after the catch survive. The TSDoc says that in one sentence.",
"options": ["A: as written, 'must not catch the refusal and carry on, because anything it does after the catch acts on a request that has already been refused'"],
"recommendation": "A, because it states the measured behaviour and invents no API."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in PR Acceptance notes, not filed: packages/qa/http-conformance/src/adapter.ts, a second IHttpRequest producer, still turns a malformed declared-JSON body into {} (catch { body = {}; }) and a text body into a string. That is the pre-#24005 invented-{} shape the standard transport now refuses. The new TSDoc says an implementation MAY refuse, so this does not violate the contract; it is a parity drift for the conformance harness. Dedupe words: http-conformance adapter malformed JSON body {}; conformance body refusal parity; IHttpRequest body producers diverge"
]
}
ACCEPT → PR #24068 comment 6226515673 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T23:21Z · contract review (CONTRACT_REVIEW_TIER) PASS 6226498420 on the same head 2a55ea381b; the head is green and goes to the queue in this act.
IHttpRequest.body's TSDoc now says what a handler reads (parsed JSON or form fields, {} with no body). It also says that reading it may refuse: the standard transport answers 400 INVALID_REQUEST itself, so a handler must not catch the refusal and carry on. A handler that needs raw bytes reads rawBody(). Both open questions were settled A. At landing this seat files the conformance adapter's divergence from the new text as a domain:cli card. The cloud dev session is archived at this ACCEPT.
both changed files have the same blobs at 8a83cebc15 as at the reviewed head 2a55ea381b (ACCEPT 6226515673, contract review PASS 6226498420)
queue branch
no gh-readonly-queue/main/pr-24068-* ref remains
mis-close scan
Fixes #24043 is the body's only closing keyword, and the squash message carries no other; this card closed by that PR, as intended, and nothing else did
Filed at landing, as the ACCEPT said:#24083. The conformance suite's reference adapter (packages/qa/http-conformance/src/adapter.ts) diverges from the new body text on three counts: malformed declared JSON becomes {}, a multipart body becomes {}, and a text/* body becomes a string. Its lane and grade are triage's; the filer's guess is domain:cli. That card also carries the review's optional wording rider ("a file upload") and the unmeasured multipart rawBody() note.
pm:dispatched is cleared in this act. The cloud dev session was archived at the seat's ACCEPT.
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
documentation·priority:p3·domain:spec·area:api·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T22:01ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
IHttpRequest.body's TSDoc) is now incomplete. Since PR fix(plugin-hono-server,hono,cloud-connection)!: refuse an unparseable request body with 400 INVALID_REQUEST instead of reading it as {} #24005 a read can throw a 400 refusal. Nothing at runtime changes. The fleet's queue currently runs p1 and p2.objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 19 (
batch 2) · 2026-10-16T22:23Z ·IHttpRequest.body's TSDoc inpackages/spec/src/contracts/http-server.tsstates what a reader can receive and that reading it may refuse, as PR #24005 made the standard transport do; TSDoc only, no type change. One PR,Fixes #24043.Session:
session_01HjR8RUwTPQ3TgbfKihmxp9(the dispatching seat; the dev runs as a cloud session the seat post names)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-24043-http-request-body-tsdocWorktree:
objectstack-issue-24043(in the cloud session's container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (paths on
origin/maine246f06119or later; stop on a breach and explain it in the report):packages/spec/src/contracts/http-server.ts: theIHttpRequest.bodyTSDoc (:23today), and therawBodyTSDoc (:31-:36) only if it then contradicts the newbodytext.patchchangeset for@objectstack/spec, since the TSDoc ships in the package's.d.ts.plugin-hono-server,qa/http-conformanceorrest.Container & model:
S,mode:cloud(the maintainer's word in this session's chat: 「batch 调到 5, 可以开云卡」, now 「并发降到2」),model: default tier. Review: aCONTRACT_REVIEW_TIERcontract review before the queue (non-test prose underpackages/spec/src/**).Clause-②: no
Responsibility: n/a — a published contract sentence PR fix(plugin-hono-server,hono,cloud-connection)!: refuse an unparseable request body with 400 INVALID_REQUEST instead of reading it as {} #24005 made incomplete, filed by
domain:cliseat 2 from that PR's contract review (6222965551, residual 6)Thread-read: 6225531438
Ruling-ref: n/a (the card's done-when; triage
6225531438)Serial constraints cleared:
batch 2: no other dev of this seat is in flight. lint(test):packages/lint's vitest inlines the linked@objectstack/specdist, so the first test file to import a lint entry pays ~10 s of cold transform — externalising it measured the package's test hold at 60 s against 98 s #23900's PR test(lint): load the linked @objectstack/spec dist with Node, not vite's transform #24048 and report-test-timings marks rows "replayed" per package, not per turbo task: a cache hit onspec:testlabels the executedtest:reporows replayed, so they drop out of the test-budget window #24017's PR fix(scripts): report-test-timings marks a row replayed only when its own turbo task replayed #24052 are ACCEPTed and in the queue; both dev sessions are archived.packages/spec/src/contracts/http-server.ts. No open claim names it.check-governed-merges.mjs --teston the claimed paths → NOT governed.objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 24043, "status": "done", "branch": "claude/issue-24043-http-request-body-tsdoc", "pr": "https://github.com/objectstack-ai/objectstack/pull/24068", "session": "session_01KNNm3SRPyw288E5DT4qVGx", "premise_still_valid": true, "summary": "IHttpRequest.body's TSDoc (packages/spec/src/contracts/http-server.ts) now covers each done-when clause. It says the body is the parsed JSON document or form fields, {} when there is no body, and that reading it may refuse. The standard transport (plugin-hono-server) throws on a body it cannot parse or a non-empty body in a media type it does not read, and answers 400 INVALID_REQUEST itself, replacing what the handler wrote, so a handler must not catch the refusal and carry on. A handler that needs bytes reads rawBody(). The text is phrased as what an implementation MAY do, not as every producer's behaviour. TSDoc only: no type change; rawBody's TSDoc unchanged (it does not contradict the new text); one prose-only patch changeset for @objectstack/spec. Draft PR #24068 is open, assigned to os-elon-musk, and the body read back byte-identical (6684 bytes).", "tests": "At head 2a55ea38 (same tree as 8026677d, where the gates ran; only the trailer was reworded). pnpm --filter @objectstack/spec build, under os-verify-lock: VERDICT command-exit 0. After the build, grep -rl 'must not catch the refusal and carry on' dist matches dist/contracts/index.d.ts and index.d.mts; the positive control 'Raw body accessor for binary uploads' matches the same two files; the old sentence 'parsed — JSON or form data when applicable' matches 0 files in dist; dist is in files[]. pnpm --filter @objectstack/spec typecheck: VERDICT command-exit 0. No behaviour changed, so no test was added and no ablation was run (TSDoc only).", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 78 commands. I ran all 78, each exit code captured before any pipe, plus 5 roster families whose roster sits under a directory of this diff (check-changeset-fixed, spec check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity): 79 exit 0, 4 exit 3. The four exit-3 results are PREREQUISITE NOT MET and are NOT MEASURED, not failures: check-plugin-teardown-shape --self-test (the shallow clone cannot reach pinned control 621a4876), lint check:doc-formula-expressions (@objectstack/formula not built), check:dual-build-cjs-loads and check:lean-entry-closure (workspace dist not built). None of them reads http-server.ts or a changeset. --ran reconciliation with exit codes: '78 derived famil(ies) accounted for — 74 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3)', exit 0. Also: spec check:generated exit 0; ESLint --no-inline-config over both changed files exit 0 (the .md file is ignored by the config, a warning only). CI check-runs on PR #24068: in_progress at report time; per the contract I did not wait for them.", "mcp_calls": "3 — mcp__github__issue_read (get), mcp__github__issue_read (get_comments), mcp__github__pull_request_read (get); all reads, no write tool", "api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each a POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft forced) + POST /repos/objectstack-ai/objectstack/issues/24068/assignees; (2) this report = POST /repos/objectstack-ai/objectstack/issues/24043/comments. git push x3 (an empty-branch probe, one push refused by the pre-push trailer hook, then the landing push); a push is not a REST write. No label-write: a changeset is present, so skip-changeset does not apply.", "line_budget": 30, "files_changed": ["packages/spec/src/contracts/http-server.ts", ".changeset/24043-http-request-body-tsdoc.md"], "deviations": [ "The first commit carried the harness's model-named Co-Authored-By trailer. The pre-push check:commit-card-trailers hook refused it, and I amended the unpublished tip to the model-free pair 'Claude' with noreply@anthropic.com (tree unchanged, verified with an empty git diff 8026677d HEAD). Nothing published was rewritten.", "Worktree: the cloud container's primary checkout already had the branch checked out, so I detached it and attached the existing branch to ../objectstack-issue-24043 instead of using worktree add -b from origin/main. Same base, e246f06119.", "Local narrowing: four derived families exited 3 (PREREQUISITE NOT MET: a shallow clone, or sibling packages not built) and are NOT MEASURED locally. CI runs them. spec pnpm test was not run locally: the diff is TSDoc plus a changeset with no behaviour change, so it is declared to CI's Test Core.", "I did not call subscribe_pr_activity. os-dev.md says the dispatch contract overrides the platform's PR-subscription posture." ], "open_questions": [ { "question": "Done-when clause '{} when there is no body' holds on the standard transport (request-body.ts last arm; pins 'a request with no body is {}' and the over-the-wire CONTROL), and also on qa/http-conformance. The test builder packages/rest/src/http-request-test-builder.ts leaves body undefined unless a test passes one, and the member stays optional (body?). Should the contract say '{}' unconditionally?", "options": ["A: keep the sentence as written ('{} when the request carries no body'), the member optional, and test builders free to omit it", "B: a follow-up card makes body required and the builder default to {}, which is a type change outside this card"], "recommendation": "A, because the card forbids a type change, and the builder's own header records a deliberate choice that absent means 'the fixture supplied none'. Contract-first argues for B only if a real handler is shown to break on undefined; none was measured." }, { "question": "The handler consequence. The card says a handler that catches and writes anyway 'would write after a 400'. Measured in adapter.ts runHandler: if refusedBodyRead is set, the adapter returns the declared 400 envelope in place of whatever the handler wrote (pinned by the query door, which catches the read and answers its own 500, yet the wire shows 400). So the response is discarded, and only the handler's side effects after the catch survive. The TSDoc says that in one sentence.", "options": ["A: as written, 'must not catch the refusal and carry on, because anything it does after the catch acts on a request that has already been refused'"], "recommendation": "A, because it states the measured behaviour and invents no API." } ], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted in PR Acceptance notes, not filed: packages/qa/http-conformance/src/adapter.ts, a second IHttpRequest producer, still turns a malformed declared-JSON body into {} (catch { body = {}; }) and a text body into a string. That is the pre-#24005 invented-{} shape the standard transport now refuses. The new TSDoc says an implementation MAY refuse, so this does not violate the contract; it is a parity drift for the conformance harness. Dedupe words: http-conformance adapter malformed JSON body {}; conformance body refusal parity; IHttpRequest body producers diverge" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT → PR #24068 comment 6226515673 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T23:21Z · contract review (CONTRACT_REVIEW_TIER) PASS6226498420on the same head2a55ea381b; the head is green and goes to the queue in this act.IHttpRequest.body's TSDoc now says what a handler reads (parsed JSON or form fields,{}with no body). It also says that reading it may refuse: the standard transport answers 400INVALID_REQUESTitself, so a handler must not catch the refusal and carry on. A handler that needs raw bytes readsrawBody(). Both open questions were settled A. At landing this seat files the conformance adapter's divergence from the new text as adomain:clicard. The cloud dev session is archived at this ACCEPT.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed · PR #24068 →
8a83cebc15(Fixes #24043,IHttpRequest.body's TSDoc) ·domain:specseat 1 ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T23:55Z8a83cebc15origin/maingit merge-base --is-ancestor 8a83cebc15 origin/main→ exit 08a83cebc15as at the reviewed head2a55ea381b(ACCEPT6226515673, contract review PASS6226498420)gh-readonly-queue/main/pr-24068-*ref remainsFixes #24043is the body's only closing keyword, and the squash message carries no other; this card closed by that PR, as intended, and nothing else didFiled at landing, as the ACCEPT said: #24083. The conformance suite's reference adapter (
packages/qa/http-conformance/src/adapter.ts) diverges from the newbodytext on three counts: malformed declared JSON becomes{}, a multipart body becomes{}, and atext/*body becomes a string. Its lane and grade are triage's; the filer's guess isdomain:cli. That card also carries the review's optional wording rider ("a file upload") and the unmeasured multipartrawBody()note.pm:dispatchedis cleared in this act. The cloud dev session was archived at the seat's ACCEPT.Generated by Claude Code