Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-3301Done

Integration test — the three axes narrow independently, and an unconstrained lesson still reaches every query

The story-level test, against the real Postgres the other lesson suites use. It proves the three axes end to end and pins the behaviour whose loss would be invisible.

⚠️ AMENDED 2026-08-21, mid-run, ON THE RECORD — three of the five expected sets below were wrong (planning bug MOTIR-3355). The original table listed assertion 2 as "C and D", assertion 3 as "E and D" and assertion 5 as "E returns, and so does D". Each of those omits the rows that are unconstrained on the axis being queried — E on types, C on phases, A/B/C on kinds — which are exactly the rows that must come back under the semantics MOTIR-3294 states: "empty on an axis means unconstrained on it, and the three are independent", restated by MOTIR-3300 as "a query naming only types does not constrain kinds or phases". Assertions 1 and 4 read identically under both readings, which is why the table looked coherent.

The reading is not a preference. Under the original table a lesson tagged on ONE axis vanishes from every query naming ANOTHER — so notes.html #37, tagged types: [test] by MOTIR-3299's backfill, would be invisible to every kinds or phases query, and the more precisely the corpus is tagged the less of it a planner can reach. That inverts the epic. The corrected sets are below and the same reasoning is carried at the head of the shipped suite.

Seed five rows, each present for one reason:

rowtypeskindsphaseswhy
Adesigndeepensingle value on two axes
Bcodedeepenmust NOT return for a design query
Ccode, chore, deploythe set case — overlap, not equality
Dunconstrained — must reach every query
Ebugskeletonthe widening case, and kind-only

Assert:

  1. types: [design], phases: [deepen] returns A and D; not B, C or E. (Unchanged — A matches on both axes, D is unconstrained on both, and each of B/C/E is genuinely excluded by one of the two clauses.)
  2. types: [chore] returns C, D and E — C matches only by overlap; D and E carry no types at all and are therefore unconstrained on that axis. A (design) and B (code) are excluded.
  3. phases: [skeleton] returns C, D and E, and not A or Bwidening and deepening lessons are genuinely separated, which is the axis with no predecessor. C and D carry no phases and stay eligible; A and B are deepen and must not reach a widening pass.
  4. No axes supplied returns all five — today's behaviour, unchanged.
  5. kinds: [bug] does not constrain types or phases: E matches by overlap and A, B, C and D all carry no kinds, so all five return. The clause is not vacuous — kinds: [epic], which no row claims, still excludes E while leaving the four unconstrained rows.

And, because D makes an empty selection unreachable while it is in the store, the empty-selection no-op is proved on a store seeded with A and B ALONE.

Assert on returned lesson ids, not the rendered promptSection — later stories edit that template.

Acceptance criteria

  • All assertions pass against the real test database, rows seeded through the service rather than by raw insert.
  • The suite goes red when any one axis applies an empty array instead of omitting its clause — demonstrated once per axis, with the failures in the PR body. That is the mutation that makes every unconstrained lesson unreachable, and there are now three ways to introduce it.
  • The suite ALSO goes red when any one axis drops the lesson-side cardinality(col) = 0 arm — the second, independent way to make an unconstrained lesson unreachable, and the one this run actually hit. Demonstrated once per axis.
  • mistakeTypes filtering is asserted unchanged alongside the new axes, and asserted to COMPOSE with an axis clause rather than be replaced by one.
  • The empty-selection no-op holds: axes matching no row yield an empty promptSection and no injection.
  • The axes survive the read as real arrays. A $queryRaw over a user-defined ENUM ARRAY returns the raw Postgres literal string unless it is cast, and every filter still passes in that state, so this is asserted on the read surface.
  • Only the changed files are run locally; CI runs the suite.

Context refs

  • motir-ai tests/lessonInjection.test.ts — the sibling suite whose seeding helpers and embed() mock this mirrors.
  • motir-ai src/repositories/lessonRepository.ts — the three overlap clauses under test.
  • MOTIR-3300 — the filters and the seam this exercises.
  • MOTIR-3355 — the planning bug for the amended fixture.