Skip to content

Size the plan import request limit from the parser bound (#146) - #158

Merged
mchwang merged 2 commits into
mainfrom
feat/146-plan-import-limit
Oct 10, 2026
Merged

mchwang merged 2 commits into
mainfrom
feat/146-plan-import-limit

Conversation

@mchwang

@mchwang mchwang commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Closes #146.

What changed

POST /api/plan/import refused every body over 16 KiB before parsing, though the parser accepts a 1 MiB source and the v1 schema permits plans far larger than 16 KiB.

  • web/server.ts: the import body limit is now MAX_IMPORT_REQUEST_BYTES = 6 * MAX_SOURCE_BYTES + MAX_REQUEST_BYTES (6 MiB + 16 KiB = 6,307,840 bytes). Every other POST keeps MAX_REQUEST_BYTES (16 KiB).
  • core/parse-v1.ts: exports the parser's 1 MiB bound as MAX_SOURCE_BYTES.
  • Why 6×: any JSON encoding writes each UTF-8 byte of the source as at most 6 bytes. The yaml parser accepts a raw control character in a quoted scalar, and JSON writes it as \^A. So every source within the parser limit fits, inside any envelope that fits 16 KiB with an empty source. A body within the limit whose source is above 1 MiB gets the parser's refusal (409, recorded and replayed), not a transport one.
  • Docs: docs/plan-format.md (the documented maximum) and the endpoint table in docs/implementation/runner-lifecycle.md.

The schema was not tightened: the parser's 1 MiB bound already binds before the schema's larger theoretical maximum, so the contracts agree once transport admits every parser-valid source.

Tests

New import request size (#146) block in test/runner-planning-feedback.test.ts, against the real server and Store:

  1. A schema-valid import whose request is over 16 KiB → 200, new revision.
  2. A 1 MiB YAML source of raw control characters, whose request is over 5.5 MiB → 200. Fails if the factor were 5×.
  3. A request exactly at the limit → 409 with the parser's "exceeds 1 MiB" refusal, replayed under the same action ID; revision unchanged.
  4. A request one byte over the limit → 413; revision unchanged.
  5. Eight other POST routes, including the other planning routes, → 413 at 16 KiB + 1 byte.

With the limit set back to 16 KiB, tests 1–3 fail with 413. With the import limit applied to every planning path, test 5 fails.

Validation

Gate Result
Focused (-t 146) 5/5 pass
Affected suites (runner-planning-feedback, runner-shutdown) 33/33 pass at f7d47e8
Type check (tsc --noEmit) clean at f7d47e8
Full suite, parallel project Validated head f7d47e8: 74 files, 1990/1990 pass (load average ~84). Earlier runs at intermediate checkpoint 3cb9673 (load 150–200) had different pre-merge.test.ts timing failures each time; that file passed 40/40 alone. Recorded as infrastructure, not product.
CI (test) pass on validated head f7d47e8
Real Docker not triggered: agent-isolation.yml path filters do not match this diff

Review record

Round Type / reviewer Base / head Findings What changed
1 Self-review ae9fc3d / 3cb9673 None outstanding —
2 Independent agent (fresh context) ae9fc3d / 3cb9673 (a) Low: other-POST test only covered /api/action, so the import limit leaking to every planning path would pass. (b) Low: at-limit test asserted only not 413. (c) Nit: code comment implied the parser refusal was unconditional. (a) Test now covers 8 routes. (b) Asserts 409 and replay. (c) Comment reworded. All in f7d47e8.
3 Independent agent (fresh context) ae9fc3d / f7d47e8 Clean for this diff. Pre-existing on main: a 413 with a large unread body stops server.close() from settling, and can reset the client. Info: the 6× worst case relies on the yaml library's leniency for C0 controls. Pre-existing bug filed as #157 (out of scope). Info noted; if the parser is made strict, test 2 fails, which is the intended signal.
4 Copilot (ran automatically, before the full-suite gate) ae9fc3d / f7d47e8 0 findings. Summary concern: exact-head full-suite and CI validation incomplete. No code change. Resolved by the exact-head CI and full-suite results above.

Review-lesson audit

  • Round 2 (a), (b): covered by existing rules — CLAUDE.md "Review readiness": every behavioural claim gets a test that fails if the claim is false.
  • Round 2 (c): one-off wording fix.
  • Round 3 shutdown hang: pre-existing defect, follow-up Shut down cleanly after refusing an oversized request body #157; not a review-process lesson.
  • Overall change: covered by AGENTS.md:101 (align transport limits with every payload the schema accepts).
  • No AGENTS.md/CLAUDE.md update needed.

🤖 Generated with Claude Code

mchwang and others added 2 commits October 9, 2026 23:56
POST /api/plan/import refused every body over 16 KiB, though the parser
accepts a 1 MiB source and the v1 schema permits plans far larger than
16 KiB. The import body limit is now 6 x 1 MiB + 16 KiB: any JSON
encoding writes each UTF-8 source byte as at most 6 bytes, so every
source the parser accepts fits. Other POSTs keep 16 KiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Exact-head full-suite and CI validation remain incomplete.

0 open findings

What changed in this PR

Aligns plan-import transport limits with the parser’s 1 MiB source bound.

Changes:

  • Derives a 6 MiB + 16 KiB import limit from parser constraints.
  • Adds boundary and route-isolation tests.
  • Documents the endpoint limit.
File Description
web/​server.ts Applies the expanded limit only to plan imports.
core/​parse-v1.ts Exports the parser source-size bound.
test/​runner-planning-feedback.test.ts Tests valid, boundary, oversized, and unaffected routes.
docs/​plan-format.md Documents sizing and refusal behavior.
docs/​implementation/​runner-lifecycle.md Updates the endpoint contract.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mchwang
mchwang merged commit a764e19 into main Oct 10, 2026
2 checks passed
mchwang added a commit that referenced this pull request Oct 10, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang added a commit that referenced this pull request Oct 10, 2026
* Close out Lane G status records (#145)

Lane G (G1-G4) merged in PRs #148, #150, #151 and #152. The README and
the design overview still described the Planning screen as unbuilt, and
build step 6 said "no screen yet". Record the screen as built, mark build
step 6 done, and record Lane G's release of the shared web files plus
the remaining lane F follow-up (#146) in the planning-screen document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Point lane status readers to the live table; name released files (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Clarify Lane G handoff and screen name (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tighten Lane G handoff wording (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mark lane G done in the Wave 2 table; state #146 ownership source (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the web files lane G released in the Wave 2 table (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Put the finished lane G row in the past tense (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop unneeded history from the lane G row (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Match the H row layout; cite where #146 is assigned to lane F (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* README: planning agent needs a github review, not the demo (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Clarify met G4 condition and demo limits (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Put wave 2 ownership rules in the past tense (#145)

Copilot round 1 found that the Wave 2 note and integration rule 2 still
gave G active ownership of web files the handoff says were released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record where H4a's web files went after G1 (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name G3's one web/server.ts change in the handoff records (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Put the H4a ownership bullet in the past tense (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name F's wave 2 server files in integration rule 2 (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove unneeded ownership history; quote the AGENTS.md rule (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep only supported ownership history (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Describe wave 1 config ownership as the plan's (#145)

Copilot round 2 found that rule 2 still stated C's and D's finished
wave 1 assignments as active rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mark the lane plans as the original schedule (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Link the Lane G close-out follow-ups (#155, #156) (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* List all open Lane G follow-ups in the handoff (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Note what the Plans screen does for amendments today (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Match the handoff to #155's scope (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update next steps and menu-link status for the Plans screen (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Describe the built Dismiss control in plan-format (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Match plan-format's Plans screen controls to the built screen (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mark unbuilt Plans screen design features (#155) (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Match the handoff to #155's widened scope (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name the suggestion Apply control; date the Plans DT update (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correct the amendment and Settings notes (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Qualify build step 6 status with #155's unbuilt parts (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Place the refresh line below the cards in plan-format (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Qualify the Plans screen in What to do next (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say the Plans screen has no approval step yet (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* List the approval step among #155's unbuilt parts (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Put lane H's ownership section in the past tense (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Put the H4b bullet in the past tense (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep H4b's storage note as the original plan (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep plan-format's v1 contract text; list shipped differences separately (#145)

Copilot round 3: the guide's suggestion-card wording had been changed to
match the shipped screen, which conflicts with the immutable v1 contract
(schema/versions/1/semantics.md). Restore the contract-mirroring text,
describe what the shipped screen does in its own section, and track the
non-conformance in #159.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cite #159 in status lines; note the #146 import limit (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* List the amendment-proposal gap in plan-format's shipped section (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record that #146 was fixed by PR #158 (#145)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@mchwang
mchwang deleted the feat/146-plan-import-limit branch October 12, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align plan import request limit with valid schema payloads

2 participants