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-3378

Planning bug: an acceptance criterion demanded evidence from a substrate on the OTHER side of a service boundary — "asserted against a real database" for an HTTP seam

Done
Description

Found while running MOTIR-3374 (motir-ai#280). The card's second acceptance criterion read:

If it is wired: a pass that calls validate_plan mid-generation gets a real verdict, asserted against a real database, and the pre-markPlanned validateCandidatePlan call is stated to be either redundant or complementary.

The run could not meet it as written, and the reason is structural rather than a matter of cost.

The mistake

The criterion demanded evidence from a substrate that lives on the OTHER side of a service boundary from the card's targetRepo. The card is pinned motir-ai. validate_plan is an HTTP call to motir-core's POST /api/internal/ai/validate-plan, and the tree it projects lives in motir-core's database. There is no motir-core process, schema, migration or fixture in the motir-ai checkout, so no motir-ai suite can assert against that database at all — the criterion is unavailable, not expensive.

Why it was written, and why the trap is general

It was written by analogy with the card's own predecessor. MOTIR-3374 was filed off MOTIR-3368, whose fix — the capture pair log_planning_mistake / log_planning_bug — writes to motir-ai's OWN Postgres via lessonService, and whose suite (tests/replanCaptureIntegration.test.ts) genuinely does assert against a real database. Both cards are "an optional GenerationSink method is unimplemented", so the evidence bar was carried across with the defect shape.

The two sinks differ in exactly the way the bar depends on. logPlanningMistake terminates in a local repository write; validatePlan terminates in a fetch to another service. That difference is invisible at the altitude the criterion was written at ("wire the sink, assert it works"), which is what makes this worth a rule rather than a one-off correction: the sibling card's evidence bar is exactly as portable as its SINK is, and a defect-shape match is not a substrate match.

The general rule this suggests

A criterion naming a test SUBSTRATE — a database, a live tenant, a deployed surface, a registry — is subject to the same repo-pinning discipline gate 1 already applies to a criterion naming a PATH. validate_work_item mechanises the path case as likely-repo-straddle; there is no equivalent for a substrate, and the check a human would run is the same one: does the thing this criterion measures live in this card's targetRepo?

Note this is NOT a repo straddle in gate 1's sense — the card ships wholly in motir-ai and its criteria name only motir-ai paths. The straddle is in the evidence, which is why the shipped detector does not see it. That asymmetry is the finding.

What the run did instead

Disposed of it on the record rather than silently re-scoping (run.md, a falsified premise is REPORTED): tests/planValidationSinkWiring.test.ts uses the discipline this repo already established for this exact endpoint — the contract-seam shape of tests/planValidationSeam.test.ts (Subtask 7.28.6 / MOTIR-1390). The real executor → the real sink → the real client, driven against a faithfully-recorded fixture of motir-core's shipped WorkItemValidityDto wire shape, so a key drift on either side breaks the build. Nothing about the sink is stubbed. Amended on MOTIR-3374's own record, and in motir-ai#280's body.

Acceptance criteria

  • plan-rules/phase-deepen.md (the gate-14 AC-axes limb, beside the existing precondition and NEGATIVE/ENUMERATION limbs) carries a SUBSTRATE limb: a criterion naming the thing it will be measured against — a database, a deployed surface, a live tenant, a registry, a platform API — owes the check that that substrate is reachable from the card's targetRepo. Where it is not, the criterion names the evidence that IS available at that boundary instead.
  • The limb states the discriminator plainly, because the shipped likely-repo-straddle detector does NOT cover this: gate 1 checks the PATHS a criterion names; this checks the SUBSTRATE it measures against, and a card can pass the first while failing the second (MOTIR-3374 did).
  • It names the carrier: a criterion inherited from a sibling card by defect-shape analogy. The sibling's evidence bar travels only as far as its sink does — a local write and a cross-service call are the same shape of defect and different shapes of proof.
  • The MOTIR-3374 fixture is cited by key, with both sides: logPlanningMistake → motir-ai's own Postgres (assertable) vs validatePlan → an HTTP call into motir-core (not assertable from here).
  • A notes.html lesson is added for the same finding — the corpus's LESSON half, per plan-rules/CORPUS-MAINTENANCE.md; this card is the RULE half.

Context refs

  • MOTIR-3374 · motir-ai#280 — the run, the amendment comment, and tests/planValidationSinkWiring.test.ts
  • MOTIR-3368 · motir-ai#277 — the sibling the bar was carried from, and tests/replanCaptureIntegration.test.ts, which does assert against a real database
  • motir-ai tests/planValidationSeam.test.ts — the contract-seam discipline used instead
  • plan-rules/phase-deepen.md — the NEGATIVE and ENUMERATION limbs this parallels
  • plan-rules/core.md gate 1 — the repo column, criterion by criterion, and what it does and does not reach