MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3355

Planning bug: a test card's fixture table encoded axis semantics that contradict the three-axes-are-independent rule its own parent story states in prose

Done
Description

Found while running MOTIR-3294 (motir run on the parent, branch parent/MOTIR-3294-lesson-axes in motir-ai). The defect is in the PLAN, not in shipped code; the correction is already applied to MOTIR-3301 on the record.

What the plan said, in two places, and they disagree

The prose, three times:

  • MOTIR-3294 (the story): "All three are set-valued, empty means unconstrained" and "empty on an axis means unconstrained on it, and the three are independent."
  • MOTIR-3299: "empty on an axis means unconstrained on it"; "The new axes are independent — a lesson may constrain one and leave the others empty."
  • MOTIR-3300: "The three axes are independent: a query naming only types does not constrain kinds or phases."

The fixture table, in MOTIR-3301, seeds five rows and lists an expected set per query. Three of those five expected sets are only reachable under the OPPOSITE semantics — that a lesson is universal only when it is unconstrained on ALL THREE axes, and otherwise must overlap on whichever axis is queried:

assertioncard expectscorrect under the prosethe difference
2 · types: [chore]C, DC, D, EE carries no types, so it is unconstrained on that axis
3 · phases: [skeleton]E, DC, D, EC carries no phases
5 · kinds: [bug]E, DA, B, C, D, EA, B and C carry no kinds

Assertions 1 and 4 are identical under both readings, which is why the table looks coherent read top to bottom.

Why the prose is the correct half

Not a preference — the fixture's reading inverts the epic:

  • MOTIR-3299's backfill tags 59 of 69 base lessons, most on a SUBSET of the axes. Under the fixture's reading notes.html #37 (tagged types: [test]) becomes invisible to every kinds or phases query, and the more precisely a lesson is tagged the less of the corpus a planner can reach.
  • Every lesson captureMistake has ever written carries all three axes empty. Those keep working under either reading, but under the fixture's reading they are the ONLY rows a filtered query can ever reach.

So tagging the corpus would REDUCE retrieval, which is the over-injection this epic exists to remove, rebuilt inside the mechanism meant to fix it.

Disposition applied

  • listForInjection implements the prose: per axis, AND (cardinality(col) = 0 OR col::text[] && ARRAY[…]::text[]), and the clause is omitted entirely when the QUERY leaves that axis empty. Empty means unconstrained on BOTH sides.
  • MOTIR-3301's three expected sets are amended on the record, with the evidence, and the amendment is repeated as a comment at the head of tests/lessonAxisFiltering.test.ts so a later reader meets it where the numbers are.
  • Six mutants (three per failure mode, one per axis) are killed by the suite.

Acceptance criteria

  • A planning rule exists requiring that when a card states an expectation as a TABLE OF EXPECTED RESULTS, the table is checked row by row against the semantics its parent states in prose — because a table that is right on two rows out of five reads as verified.
  • The rule names the tell: an expected set that omits the members which are UNCONSTRAINED on the queried axis. A fixture author enumerating "what this query is FOR" writes down the rows that match and forgets the rows that do not object.
  • The notes.html entry recording this is linked from the rule.

Context refs

  • motir-meta prompts/plan-rules/phase-deepen.md — where an authoring bar for a card's own acceptance table belongs.
  • motir-ai tests/lessonAxisFiltering.test.ts — the amended table and the reasoning, as shipped.
  • MOTIR-3301 — the card amended.