A total re-cut of the planning rule corpus and its selector. Three incremental cards (MOTIR-3853, MOTIR-3866, MOTIR-3869) improved the existing shape and, between them, established that the shape itself is wrong. This story replaces it.
type is an output of skeleton, not deepen. Today propose_node carries no type and deepen_node sets it, so on a fresh plan the selector reads a field the skeleton never wrote — the type axis selects nothing that a plan pass can act on.skeleton or deepen.skeleton, packs are selected by KIND. Nothing else.deepen, packs are selected by kind OR type — never a cross-product of the two.
epic / story / bug ⇒ no type; select by KIND.task / subtask ⇒ the type is set; select by TYPE.Point 4 moves bug out of the typed set for rule-selection purposes. It is in TYPED_KINDS today alongside task and subtask.
legalPlanningRuleCells() enumerates kind × type at deepen; under the model above a deepen cell is either a kind cell or a type cell, so the space is a sum, not a product.VERIFY_EVERY_PRECONDITION reaches every deepen cell and tells a translate card to run pg_policies, pg_roles, information_schema and row_security_active(). Subject mentions across that one constant: HTTP seam 24, design asset 23, graph 16, datastore 11, test lane 9. MOTIR-3856 tried to fix this by authoring a precondition rule per type; the PR was closed, because adding rules does not stop the wrong ones being delivered.tests/treeGeneration.test.ts's clauseOf states it: "clauses are one line each in the constant", and a family of guards asserts specific limbs sit adjacent to named neighbours because each reads back to the one before it. Splitting a constant changes what counts as a rule. Conservation cannot see it — it compares words and length, and a space becoming a newline changes neither.That last point is the real blocker and it is a DECISION, not an implementation detail: it changes a recorded invariant, so it is settled on its own card before any text moves.
skeleton receives the packs its KIND selects and no type pack at all; a pass at deepen receives the packs its kind selects (epic/story/bug) or its type selects (task/subtask), and never both.type is set at skeleton and is an input to deepen, so a fresh plan's deepen pass composes the type's packs. Proven end-to-end on a generation pass, not only on the resolver.translate, copy or legal card receives contains no datastore, HTTP-seam or design-asset discharge — asserted, not reviewed by eye.motir-meta's pack tree carries the same selector and the same cuts, and its MANIFEST is regenerated rather than hand-edited.motir-ai src/llm/planningRulePacks.ts — resolvePlanningRulePacks, legalPlanningRuleCells, PACKS_BY_TYPE, CORPUS_ORDER.motir-ai src/llm/workItemTypes.ts — TYPED_KINDS (task, bug, subtask), CONTAINER_KINDS.motir-ai src/llm/treeGeneration.ts — propose_node / deepen_node tool schemas, deepenSelectorFor, buildGenerationSystemPrompt.motir-ai tests/treeGeneration.test.ts — clauseOf and the limb-adjacency guards.motir-meta prompts/plan-rules/split.py + DECISION.numbers.py — the generated MANIFEST and its pre-split row model.