(motir-core) `createTestWorkItem` writes a ZERO-PADDED `position`, so the next TOP-LEVEL create in that project 500s on `invalid order key head: 0` — the exact trap `scripts/plan-seed/seed.ts` warns about in prose
Found at run time by motir run MOTIR-1854, building 11.4.6's conformance guard. An out-of-scope finding logged rather than absorbed (notes.html #27): the guard was rewritten to seed through the real route, so nothing in Story 11.4 depends on the fixture — this card is the defect itself.
Reproduction — a failing case, not a code-reading theory
const caller = await createV1ProjectCaller({ scopes: ['read', 'work_items:write'] });
await createTestWorkItem(caller.fixture, { kind: 'story', title: 'A story' });
// POST /api/v1/projects/{projectKey}/work-items — a TOP-LEVEL create
// → 500 {"error":"Internal server error."}
// stderr: [api/v1] unhandled error … Error: invalid order key head: 0
Run against origin/main + this story's branch; it reproduces on either.
The cause
tests/fixtures/workItemFixtures.ts:125 writes the row directly through the repository with
position: String(key).padStart(6, '0'), // → "000001"
workItemsService.createWorkItem (lib/services/workItemsService.ts:819-820) reads the LAST SIBLING's position and calls keyForAppend(lastPosition) → generateKeyBetween("000001", null). The fractional-index library rejects "000001" outright: a valid key's integer part must not begin with 0. So the create throws a raw Error, classifyApiV1Error does not recognise it, and the wrapper renders a bare 500 — correctly, per ADR §4.
It is scoped to SIBLINGS, which is why it has stayed dormant. createWorkItem computes the append position among the new item's siblings (parent-scoped, top-level when parentId is null). Every shipped suite that creates through the API after seeding with the fixture creates a CHILD of the seeded row — e.g. tests/api/v1/work-item-write-routes.test.ts:108-113 seeds a parent and posts a subtask with parentKey — so the sibling list is empty and keyForAppend(null) is fine. The first suite to create a top-level SIBLING of a fixture-seeded row hits it.
Why this is worth a card rather than a note
The codebase has already learned this exact lesson once, in prose, in the other seeder. scripts/plan-seed/seed.ts:396-405:
"A VALID, GLOBALLY-UNIQUE fractional-index
positionper item…positionMUST be a real fractional-index key (the shapelib/workItems/positioning.tsmints) — NOT a zero-padded number."
The seed script was fixed and documented; the test fixture was not, and there is nothing that would tell the next author. The failure it produces is maximally unhelpful: a 500 with no code, from a create that looks correct, in a suite whose subject is something else entirely — the finding costs its discoverer an hour and teaches them nothing they can reuse.
What the fix is
createTestWorkItem should mint its position the way the product does — keyForAppend(lastSiblingPosition) from lib/workItems/positioning.ts, chained across the items a fixture creates, exactly as scripts/plan-seed/seed.ts already does. The padded string is not load-bearing anywhere: position is compared as an opaque string in ORDER BY position ASC, so the only property the fixture needs is that siblings sort in creation order, which a real fractional-index key gives.
Check the blast radius before changing it. createTestWorkItem is used across the suite, and any test asserting a literal "000001" position — or asserting an ORDER that only holds under zero-padded numeric strings — has to move with it. That is the work of this card, and it is why it is a card rather than a one-line edit.
Acceptance criteria
createTestWorkItem(and any sibling fixture that writespositiondirectly) mints a real fractional-index key vialib/workItems/positioning.ts, chained so siblings sort in creation order.- The reproduction above passes: seeding with the fixture and then creating a TOP-LEVEL item through
POST /api/v1/projects/{projectKey}/work-itemsreturns 201, asserted by a test that would have failed before the fix. - No test asserts a zero-padded
positionliteral; every ordering assertion that depended on the old format is updated, found by grep rather than by running until green. - The fixture carries the same warning
scripts/plan-seed/seed.ts:396-405does, so the next author cannot reintroduce it. - The full suite passes.
Context refs
tests/fixtures/workItemFixtures.ts:125— the padded write.lib/services/workItemsService.ts:812-825—keyForAppend(lastPosition), sibling-scoped, and where it throws.lib/workItems/positioning.ts—keyForAppend/keyBetween, the only legitimate source of aposition.scripts/plan-seed/seed.ts:396-405— the same lesson, already learned and written down for the other seeder.tests/api/v1/work-item-write-routes.test.ts:107-127— the shipped test whose CHILD create is why this stayed dormant.tests/api/v1/openapi-drift-guard.test.ts— the suite that hit it, and the comment recording why it seeds through the real route instead.