Found while writing the Data Modeling import-mappings page (epic #10206, card #10213). Filed unassigned for domain:spec triage; not fixed there, per that card's non-goals.
Enforced but undeclared
mappingName is the request parameter the mapping metadata kind exists for — it is the consumer that satisfied the ADR-0088 admission test (#2611). Both import routes read it:
POST /api/v1/data/:object/importPOST /api/v1/data/:object/import/jobs
Both go through prepareImportRequest in packages/rest/src/import-prepare.ts, which reads body.mappingName off the raw body, resolves the artifact, and rejects mappingName plus an inline mapping with 400 CONFLICTING_MAPPING.
ImportRequestSchema in packages/spec/src/api/export.zod.ts declares fifteen keys and none of them is mappingName. CreateImportJobRequestSchemaisImportRequestSchema (one object, two names), so the async route is undeclared in the same edit.
This is the mirror of declared-but-unenforced — the shape #6245 closed for webhook/connector/sharing_rule, in the opposite direction: the wire accepts something the contract does not describe.
Measured
Zero occurrences in the built declarations:
$ grep -rn "mappingName" --include="*.d.ts" packages/spec/dist/ # exit 1, 0 lines
And the SDK call is a type error. packages/client/src/index.ts types both data.import and the scoped data.import as (object: string, request: ImportRequest), with ImportRequest imported from @objectstack/spec/api:
importtype{ImportRequest}from'@objectstack/spec/api';constreq: ImportRequest={format: 'csv',csv: 'Full Name,E-mail\nAda,ada@example.com\n',mappingName: 'showcase_inquiry_feed',};error TS2353: Object literal may only specify known properties, and 'mappingName'
does not exist in type '{ format?: "csv" | "json" | "xlsx" | undefined; csv?: string
| undefined; rows?: Record<string, unknown>[] | undefined; ... 9 more ...;
skipBlankMatchKey?: boolean | undefined; }'.
(tsc 6.0.3, --strict, against packages/spec/dist built at this branch's base.)
Why it matters beyond a missing key
The kind was promoted because a consumer landed. As things stand that consumer is reachable only by hand-rolled HTTP: a TypeScript caller using the published client cannot name a mapping without casting the request. Anything else derived from the spec — the OpenAPI document, any generated client — is missing it for the same reason.
Suggested shape
Add to ImportRequestSchema, next to mapping:
mappingName: z.string().optional().describe('Name of a registered `mapping` artifact to apply. Mutually exclusive with `mapping`.'),Worth deciding at the same time whether the mutual exclusion should be a schema refinement rather than only a route check — the route's 400 CONFLICTING_MAPPING stays either way, but a .refine() would reject the conflicting pair at authoring time for anyone building the body through the schema.
Note ImportRequestSchema is a plain z.object, so it strips unknown keys rather than rejecting them; adding the key is additive and cannot break an existing caller.
Impact on docs
content/docs/data-modeling/import-mappings.mdx (card #10213) documents the HTTP request, which works, and carries an explicit warning that the SDK call does not type-check, pointing at this issue. That warning should be deleted when this lands.
Generated by Claude Code
Found while writing the Data Modeling import-mappings page (epic #10206, card #10213). Filed unassigned for
domain:spectriage; not fixed there, per that card's non-goals.Enforced but undeclared
mappingNameis the request parameter themappingmetadata kind exists for — it is the consumer that satisfied the ADR-0088 admission test (#2611). Both import routes read it:POST /api/v1/data/:object/importPOST /api/v1/data/:object/import/jobsBoth go through
prepareImportRequestinpackages/rest/src/import-prepare.ts, which readsbody.mappingNameoff the raw body, resolves the artifact, and rejectsmappingNameplus an inlinemappingwith400 CONFLICTING_MAPPING.ImportRequestSchemainpackages/spec/src/api/export.zod.tsdeclares fifteen keys and none of them ismappingName.CreateImportJobRequestSchemaisImportRequestSchema(one object, two names), so the async route is undeclared in the same edit.This is the mirror of declared-but-unenforced — the shape #6245 closed for
webhook/connector/sharing_rule, in the opposite direction: the wire accepts something the contract does not describe.Measured
Zero occurrences in the built declarations:
And the SDK call is a type error.
packages/client/src/index.tstypes bothdata.importand the scopeddata.importas(object: string, request: ImportRequest), withImportRequestimported from@objectstack/spec/api:(tsc 6.0.3,
--strict, againstpackages/spec/distbuilt at this branch's base.)Why it matters beyond a missing key
The kind was promoted because a consumer landed. As things stand that consumer is reachable only by hand-rolled HTTP: a TypeScript caller using the published client cannot name a mapping without casting the request. Anything else derived from the spec — the OpenAPI document, any generated client — is missing it for the same reason.
Suggested shape
Add to
ImportRequestSchema, next tomapping:Worth deciding at the same time whether the mutual exclusion should be a schema refinement rather than only a route check — the route's
400 CONFLICTING_MAPPINGstays either way, but a.refine()would reject the conflicting pair at authoring time for anyone building the body through the schema.Note
ImportRequestSchemais a plainz.object, so it strips unknown keys rather than rejecting them; adding the key is additive and cannot break an existing caller.Impact on docs
content/docs/data-modeling/import-mappings.mdx(card #10213) documents the HTTP request, which works, and carries an explicit warning that the SDK call does not type-check, pointing at this issue. That warning should be deleted when this lands.Generated by Claude Code