Planning bug: `blocks` does not cross the AI boundary either — MOTIR-4055 enumerates THREE missing link kinds and there are FOUR
Opened by Zhu Yue ·
What the plan says
MOTIR-4055 enumerates the neighbourhood's links as blocked_by · blocks · relates_to · duplicates · clones and then states which of them the AI boundary cannot carry:
relates_to,duplicatesandclonesDO NOT CROSS THE AI BOUNDARY.
MOTIR-4063 — the producer half — is scoped to exactly those three, in its title and in its body. MOTIR-3942 says the same: "Three of them cannot cross the AI boundary today."
What is true
FOUR of the five cannot. blocks is missing for precisely the same reason as the other three, and nobody checked it because blocked_by is available and the two read as one pair.
Verified on origin/main, 2026-09-01:
get-item→aiBoundaryService.getItem→workItemsService.getWorkItemByIdentifier→toWorkItemDto, the LIGHT shape. It carries no link arrays at all — notrelatesTo, notduplicates, notclones, and notblocks. The full set is assembled elsewhere inworkItemsServicefor the MCP surface only.blocked_byis available not because the item read carries it but because it has its OWN endpoint:walk-blocking→workItemsService.getBlockingClosure, whose response is{ root, nodes, edges: { blockedKey, blockerKey }, truncated }and is documented as "the transitive is_blocked_by closure of a root: what must land before this." One direction, by construction. There is nowalk-blocked-by, and no parameter that inverts it.
So the boundary's link inventory is: blocked_by yes (via its own read), blocks / relates_to / duplicates / clones no.
Why the enumeration went short — the shape, not the carelessness
The card correctly traced the LIGHT-shape defect through get-item and correctly named the three arms that read as a group (relatesTo / duplicates / clones are literally adjacent in workItemsService). blocks was then classified by its PAIR rather than by its ROUTE: blocked_by is reachable, blocks is its inverse, so blocks felt reachable. It is reachable by a different mechanism that does not have an inverse.
The general shape: an availability enumeration that groups by CONCEPT while availability is decided by ROUTE. The five link kinds are one concept and three routes — the item payload (4 kinds, all absent), a dedicated closure endpoint (1 kind, one direction), and nothing at all for the inverse.
What has already been done — the correction is applied, this is the record
Nothing is blocked. MOTIR-4055 shipped (PR #345) with the arm handled correctly rather than assumed:
NeighbourhoodLinkscarries anunavailablelist, so an absent arm is DECLARED. An emptyblocksbesideunavailable: ['blocks', …]says "not readable"; without it, it would say "there are none" — a different and false claim a planner would act on.- The consumer reads an arm the moment the boundary carries it, in either the skeleton-row shape or the MCP
{ linkId, item }shape, with no change on the motir-ai side. Asserted. readNeighbourhoodtherefore reportsunavailable: ['blocks', 'relatesTo', 'duplicates', 'clones']today, and will report fewer as the producer lands — includingblocks, free, if MOTIR-4063 widens to it.
What is left to decide, and it is a PLAN decision rather than a code one
MOTIR-4063's scope is short by one kind. Its title, body and acceptance criteria all name three. Whoever runs it should either widen it to four — the blocks half is a different read from the other three, since it is the INVERSE of an edge rather than a field on a DTO, so it is real work and not a fourth line in a list — or cut a sibling for it and say why.
Either is fine. Silence is not: with the enumeration standing at three, MOTIR-4063 can close green with blocks still unreadable and MOTIR-4062's gate criterion — "THE NEIGHBOURHOOD CARRIES ALL FIVE LINK KINDS" — unmeetable, with nothing in the graph recording why.
Acceptance criteria
- MOTIR-4063 either covers
blocksor names the card that does, on the record. The enumeration and the scope agree. - The decision states which ROUTE serves it — the item payload (a fourth arm beside the three) or an inverse of the blocking closure — because they are different work.
- MOTIR-3942's "Three of them cannot cross the AI boundary today" is corrected to four in the same pass, since the story's own gate reads on all five.
Context refs
motir-corelib/services/aiBoundaryService.ts—getItem(the light shape) andwalkBlocking(the one-direction closure).motir-corelib/services/workItemsService.ts—getWorkItemByIdentifier,getBlockingClosure, and ~4596–4733 where the full link set IS assembled for the MCP surface.motir-aisrc/core/coreClient.ts—BlockingClosureView, whoseedgesare{ blockedKey, blockerKey }of theis_blocked_byclosure.motir-aisrc/jobs/planningTarget.ts—NeighbourhoodLinks.unavailable, where this is currently absorbed correctly.
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.