Skip to content

Collapse two pieces of copy-paste plumbing - #408

Merged
dovvnloading merged 1 commit into
mainfrom
refactor/dispatch-and-domain-plumbing
Sep 4, 2026
Merged

Collapse two pieces of copy-paste plumbing#408
dovvnloading merged 1 commit into
mainfrom
refactor/dispatch-and-domain-plumbing

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Problem

store_code_review_diff took 18 parameters — the widest signature in the repo. Fifteen of them were pure transport: the intent pulled them out of the fetch bundle with .get() one at a time, only for the method to set them straight onto node.state.

start_gitlink_apply had five refusal paths that each repeated the same four lines:

node.pending_request_id = None
self._runs.release(request_id)
on_failure("...")
await bus.publish("scene")
return

Four chances each to forget one, and forgetting either of the first two is not cosmetic — a missed pending_request_id leaves the node permanently busy, a missed release() leaks the registry slot for the rest of the session.

Change

store_code_review_diff takes the bundle whole. pr_url stays a separate argument because it genuinely is not part of the bundle — it is what the user typed, and it survives the fetch that used it being superseded. The intent's call site drops from 17 lines to one. The widest signature in backend/ is now 15 (run_lifecycle.run_single_shot).

The per-field coercion moves with it into a small _bundle_int. The bundle is built from a GitHub API response, so every numeric field in it is external input; diff_fetch.py already coerces on the way in, and this is the second line of the same defence for a bundle arriving by any other route. Verified:

pr_number="twelve" -> 0    changed_files=-5 -> 0
additions="lots"   -> 0    files=[{...},"junk"] -> the junk row dropped

The five gitlink refusal paths share one _abandon(). Its await sits on the abandon path only, so the atomic check-and-freeze section keeps its zero-await guarantee — the comparisons there still run with no suspension point between them; _abandon only runs once one of them has already decided to give up. The two remaining bare cleanups in that file are the task-completion finally blocks, a different concern, correctly using the guarded if node.pending_request_id == request_id form.

Test plan

  • 12 existing store_code_review_diff call sites updated. They were already unpacking a dict (**{k: v for k, v in _bundle().items()}), so they got shorter too.
  • Full suite: 3212 passed, 19 skipped. ruff and mypy clean.

Note for the record: the _bundle_int helper first landed between the @dataclass decorator and SceneDocument, breaking the module on import — the same line-heuristic mistake as the previous change. Caught by test collection and relocated above the decorator.

🤖 Generated with Claude Code

store_code_review_diff took 18 parameters, the widest signature in the
repo. Fifteen of them were pure transport: the intent pulled them out of
the fetch bundle with .get() one at a time, only for the method to set
them straight onto node.state. It now takes the bundle whole.

`pr_url` stays a separate argument because it is genuinely not part of
the bundle - it is what the user typed, and it is kept even when the
fetch that used it is superseded. The intent's call site drops from 17
lines to one. Widest signature in backend/ is now 15
(run_lifecycle.run_single_shot).

The per-field coercion moves with it, into a small _bundle_int: the
bundle is built from a GitHub API response, so every numeric field in it
is external input. diff_fetch.py already coerces on the way in; this is
the second line of the same defence, for a bundle arriving by any other
route. Verified: pr_number="twelve", additions="lots", changed_files=-5
and a non-dict entry in files all land as 0/0/0 and a dropped row rather
than raising.

Separately, start_gitlink_apply had five refusal paths that each repeated
the same four lines - clear pending_request_id, release the registry slot,
report, publish. Four chances each to forget one, and forgetting either of
the first two is not cosmetic: a missed pending_request_id leaves the node
permanently busy, a missed release leaks the slot for the session. They
share one _abandon() now.

Its await sits on the abandon path only, so the atomic check-and-freeze
section keeps its zero-await guarantee - the comparisons there still run
with no suspension point between them. The two remaining bare cleanups in
that file are the task-completion `finally` blocks, which are a different
concern and correctly use the guarded
`if node.pending_request_id == request_id` form.

Test plan:
- 12 existing store_code_review_diff call sites updated; they were already
  unpacking a dict, so they got shorter too.
- Full suite: 3212 passed, 19 skipped. ruff and mypy clean.

Note for the record: the _bundle_int helper first landed between the
@DataClass decorator and SceneDocument, breaking the module on import -
the same line-heuristic mistake as the previous change. Caught by the test
collection, relocated above the decorator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit d657b9e into main Sep 4, 2026
5 checks passed
@dovvnloading
dovvnloading deleted the refactor/dispatch-and-domain-plumbing branch September 4, 2026 15:26
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.

1 participant