Symptom
POST /api/v1/data/showcase_task with a number in a declared text field:
answers:
400 { "error": "TypeError: not a function", "object": "showcase_task" }
— a raw JS runtime error as the client-facing message, with no code and no fields[]. Reproduced 3x.
Expected: the ledgered envelope the neighbouring invalid bodies produce on the same route in the same run:
| body | observed | |
|---|
{} | 400 VALIDATION_FAILED with fields[] | correct |
| bad enum value | 400 with invalid_option and the allowed list | correct |
{"title": 12345} | 400 "TypeError: not a function", no code | defect |
Two separate contract breaks in one response: a handler-internal TypeError reaching the wire verbatim, and an error body outside the ledgered envelope shape (a client keying on code / fields[] gets neither).
Root cause
Not fully located in the report. Its suspicion: a coercion path calls a string method on a number before validation reports the type mismatch, so the write throws a TypeError instead of producing a field error.
Investigation note from extraction (working tree checked 2026-08-11, so verify against a86db175 before acting): the observed body shape — status 400, raw message, an object key, and no code — matches no branch of mapDataError in packages/rest/src/rest-server.ts on current main. #5489 moved that mapper's terminal branch to a sanitised 500 INTERNAL_ERROR (no object key), the declared-4xx passthrough in resolveErrorResponse does not attach object, and every remaining 400 branch carries a code. Likewise validateOne in packages/objectql/src/validation/record-validator.ts handles a number in a text field safely (String(value)), so the throw is upstream or downstream of it. Locating the emitting seam is step 1 of the fix.
No domain:* label applied deliberately: the fix could land in packages/rest (envelope classification ⇒ domain:cli) or in packages/objectql (the coercion that throws ⇒ domain:engine-core), and the extraction pass could not tell which from the report plus a tree read. Label it once the throw site is found.
Reproduction
- Boot showcase on a fresh isolated file DB (
SqlDriver / better-sqlite3); authenticate as admin@objectos.ai. POST /api/v1/data/showcase_task with body {"title": 12345} (a number into the declared text field) → 400 {"error":"TypeError: not a function","object":"showcase_task"}. Repeats 3/3.- Controls on the same route: body
{} → 400 VALIDATION_FAILED with fields[]; a bad enum value → invalid_option with the allowed list.
Source
Extracted from the QA run #7463 (framework a86db17).
Symptom
POST /api/v1/data/showcase_taskwith a number in a declaredtextfield:{"title": 12345}answers:
— a raw JS runtime error as the client-facing message, with no
codeand nofields[]. Reproduced 3x.Expected: the ledgered envelope the neighbouring invalid bodies produce on the same route in the same run:
{}400 VALIDATION_FAILEDwithfields[]400withinvalid_optionand the allowed list{"title": 12345}400 "TypeError: not a function", nocodeTwo separate contract breaks in one response: a handler-internal
TypeErrorreaching the wire verbatim, and an error body outside the ledgered envelope shape (a client keying oncode/fields[]gets neither).Root cause
Not fully located in the report. Its suspicion: a coercion path calls a string method on a number before validation reports the type mismatch, so the write throws a
TypeErrorinstead of producing a field error.Investigation note from extraction (working tree checked 2026-08-11, so verify against
a86db175before acting): the observed body shape — status400, raw message, anobjectkey, and nocode— matches no branch ofmapDataErrorinpackages/rest/src/rest-server.tson current main. #5489 moved that mapper's terminal branch to a sanitised500 INTERNAL_ERROR(noobjectkey), the declared-4xx passthrough inresolveErrorResponsedoes not attachobject, and every remaining 400 branch carries acode. LikewisevalidateOneinpackages/objectql/src/validation/record-validator.tshandles a number in atextfield safely (String(value)), so the throw is upstream or downstream of it. Locating the emitting seam is step 1 of the fix.No
domain:*label applied deliberately: the fix could land inpackages/rest(envelope classification ⇒domain:cli) or inpackages/objectql(the coercion that throws ⇒domain:engine-core), and the extraction pass could not tell which from the report plus a tree read. Label it once the throw site is found.Reproduction
SqlDriver/ better-sqlite3); authenticate asadmin@objectos.ai.POST /api/v1/data/showcase_taskwith body{"title": 12345}(a number into the declaredtextfield) →400 {"error":"TypeError: not a function","object":"showcase_task"}. Repeats 3/3.{}→400 VALIDATION_FAILEDwithfields[]; a bad enum value →invalid_optionwith the allowed list.Source
Extracted from the QA run #7463 (framework a86db17).