@@ -102,11 +102,13 @@ known case: accepting a `repo:objectui` PR ⇒ file a `pm:queue` issue in
102102` objectstack ` — "run ` pnpm objectui:refresh ` and land the console bump",
103103referencing the merged PR, blocked-by it until it actually merges.
104104
105- ** 4. Multiple PM sessions shard by repo — never share one queue.** The
106- claim protocol makes concurrent PMs * safe* , not * useful* : batch
107- independence (file-disjointness) is only checked within one PM's view, so
108- two PMs on the same queue can claim different issues that collide on
109- shared files, and the merge queue is one lane regardless. Scaling order:
105+ ** 4. Multiple PM sessions shard by repo; one shared queue only under
106+ domain lanes.** The claim protocol makes concurrent PMs * safe* , not
107+ * useful* on its own: batch independence (file-disjointness) is only checked
108+ within one PM's view, so two PMs on the same queue can claim different
109+ issues that collide on shared files, and the merge queue is one lane
110+ regardless. Making that check ** global** is exactly what the next section
111+ does. Scaling order:
110112
1111131 . One PM, bigger batch (` batch:5 ` is the maintainer's chosen operating
112114 point, riding on the resource discipline above), heavy tasks via
@@ -115,7 +117,13 @@ shared files, and the merge queue is one lane regardless. Scaling order:
115117 repo** as its shard (` /pm-dispatch repo:objectstack-ai/objectui ` ) —
116118 file universes are disjoint by construction. A sharded PM states its
117119 shard in every claim comment and ** never claims outside it** .
118- 3 . Multiple PMs on the SAME queue: prohibited — all cost, no throughput.
120+ 3 . Multiple PMs on the SAME queue: ** prohibited unless the Domain-lanes
121+ protocol (next section) is active** — every PM in its own session and
122+ its own container, domain sets registered in the registry issue, label
123+ discipline observed, and the global in-flight check run at every batch
124+ selection. Without that protocol the ban stands as written: all cost,
125+ no throughput, and the collision stays invisible to both PMs until the
126+ merge.
119127
120128** Shard ownership is registered, never assumed.** A registry issue in the
121129main backlog (` [PM] 分片分工登记表 ` ) records which session owns which
@@ -159,6 +167,81 @@ gives the maintainer a single view (filter by `repo:*` and `pm:*`). The PM
159167maintains no tracking state outside GitHub — that invariant is what keeps
160168the loop resumable and the board honest.
161169
170+ ## Domain lanes(同仓多 PM 并发)
171+
172+ Rule 4's ladder ran out at one PM per repo because file-disjointness is only
173+ ever checked inside one PM's own view. Domain lanes are the ** third rung** :
174+ one PM's triage verdict is cached as a ` domain:* ` label every other PM can
175+ read, so batch selection filters at the label layer instead of at the merge.
176+ Premise: each PM is its ** own session in its own container** — adding a PM
177+ adds compute, not contention — and collisions are prevented by the
178+ domain→package mapping, not by hoping two PMs pick different work.
179+
180+ ** Anchoring rule.** The whole scheme rests on this one sentence:
181+
182+ > Every package belongs to exactly ** one** domain; an issue's ` domain:* `
183+ > label is the domain of ** the package the fix lands in** , decided at triage
184+ > by reading the code — ** never guessed from the issue's title vocabulary** .
185+
186+ The counter-example that makes it a rule: #4775 is a hook ` condition ` , which
187+ reads as automation, but the fix lands in
188+ ` packages/objectql/src/hook-wrappers.ts ` ⇒ ` domain:engine ` . Labeling by topic
189+ would have routed it to a different PM than the one already inside that
190+ package — the exact collision lanes exist to prevent. If you cannot say which
191+ file the fix touches, you have not triaged it yet, and it is not labelable.
192+
193+ | 标签 | 包家族 |
194+ | :--| :--|
195+ | ` domain:engine ` | ` packages/objectql ` 、` packages/metadata* ` 、` packages/platform-objects ` 、` packages/core ` 、` packages/plugins/driver-* ` |
196+ | ` domain:services ` | ` packages/services/* ` 、` packages/plugins/plugin-approvals ` 、` plugin-webhooks ` 、` packages/connectors/* ` |
197+ | ` domain:identity ` | ` packages/plugins/plugin-auth ` 、` plugin-security ` 、` plugin-sharing ` 、` plugin-audit ` |
198+ | ` domain:devx ` | ` packages/lint ` 、` skills/** ` 、` content/docs/** ` 、` scripts/ ` (门禁类) |
199+ | ` domain:spec ` | ` packages/spec ` 及其生成物(现 spec 车道不变) |
200+ | ` domain:cli ` | ` packages/cli ` 、` runtime ` 、` verify ` 、` qa ` 、` types ` |
201+
202+ ` examples/** ` belongs to the subsystem it exercises; anything that fits
203+ nowhere is judged at triage by its principal landing site. A package missing
204+ from the table is classified the first time it is triaged and the table
205+ updated ** by PR** — the taxonomy evolves deliberately, never per-claim.
206+
207+ ** Label discipline.** ` domain:* ` is applied during the backlog sweep (round
208+ loop step 0) by whichever PM triages the issue. ** Labeling ≠ claiming** : any
209+ PM may label any issue, including ones it will never claim — the label is
210+ shared routing, not a reservation. An ** unlabeled issue may not be claimed by
211+ anyone** : triage and label it first, or selection has silently gone back to
212+ happening inside one PM's private view.
213+
214+ ** Claim scope.** Each PM session registers its ** domain set** in the registry
215+ issue (` [PM] 分片分工登记表 ` , #4604 — the same registry that records repo
216+ shards) and claims only issues whose label falls inside that set. A set, not
217+ a single domain: lanes are a routing table, not a job title.
218+
219+ ** Cross-domain issues.** Prefer the contract-first split of rule 2 — one
220+ sub-issue per domain, each carrying its own ` domain:* ` label, ordered with
221+ ` Blocked-by: ` . When a split costs more than it buys, a single PM claims the
222+ whole issue and ** declares the full file surface** in its claim comment, so
223+ every other PM's in-flight check can see all of it.
224+
225+ ** Borrowing.** An idle PM may claim outside its registered set when all three
226+ hold: (a) that domain's PM has not claimed the issue, (b) the claim comment
227+ declares the file surface, (c) the global in-flight check below passes.
228+ Borrowing is a one-issue exception, not a lane transfer — the registry entry
229+ does not change, so nobody has to guess who owns the domain afterwards.
230+
231+ ** Global in-flight check — run it at batch selection (step 3).** List every
232+ ` pm:dispatched ` issue across the repo, read the file-surface declaration on
233+ each one's latest claim comment, and require your candidates to be disjoint
234+ from all of them. This is step 3's independence test raised from your batch
235+ to the whole repo; skip it and two individually-independent batches are
236+ jointly dependent, which is precisely the failure the same-queue ban was
237+ protecting against.
238+
239+ ** The merge queue is still one shared serial resource.** Lanes buy parallel
240+ authorship, not parallel landing: the flaky-test tax (#4796 ) scales linearly
241+ with the number of PMs, and a red queue blocks every lane at once. Queue
242+ health is therefore a shared duty — a PM that notices a flake fixes or files
243+ it rather than re-queuing past it, whichever lane it came from.
244+
162245## The round loop
163246
164247### 0. Backlog sweep — classification is a standing duty, not a request
@@ -280,6 +363,12 @@ execute atomically, in order:
280363 > 会话:` session_<id> `
281364 > 分支:` claude/issue-<n>-<slug> `
282365 > Worktree:` <repo>-issue-<n> `
366+ > 域:` domain:<x> `
367+ > 文件面:` <预计触碰的目录列表> ` (越界即停,报告说明)
368+
369+ 「文件面」is ** required** for cross-domain and borrowed claims and
370+ ** recommended** for ordinary same-domain ones — it is the only input
371+ another PM's global in-flight check has to read.
2833723 . ** Race check** : assignment is idempotent, so two agents can both
284373 "succeed". Re-read the comments; if an earlier claim comment with a
285374 * different* session ID or branch name exists, you lost — touch nothing of
0 commit comments