Repository navigation
fix(rest)!: apply an inline import mapping entry's transform, defaultValue and required - #25540
objectstack-fleet[bot] wants to merge 3 commits into
Conversation
…ue and required Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7560ce704adfd5656edf593ae5c510ae4a2565c0 && git checkout 7560ce704adfd5656edf593ae5c510ae4a2565c0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 74496c64b0637103f87cceec3f37932a48813e65 b5a3141e7f0a2efdf94e9ae94b88d935cf96f37a && git checkout -B drift-repro 74496c64b0637103f87cceec3f37932a48813e65 && git merge --no-ff b5a3141e7f0a2efdf94e9ae94b88d935cf96f37a
node scripts/docs-audit/affected-docs.mjs --json 74496c64b0637103f87cceec3f37932a48813e65
|
Contract reviewServed-tier: Inputs read: card #25491 (body + 3 comments: triage 6370310580, claim 6371842583, os-dev-report 6374589262), PR #25540 body and file list (3 paths), ① Derived judgmentsAccept-set and public-surface changes the diff implies, read in the cited code:
② Semver level
Judged against the rule: the kind is Clause-②: no (narrowing) ③ Boundary flagsDev flags (os-dev-report 6374589262,
Not flagged by the dev, found here: ①-6 (the third door the narrowing reaches is unnamed) and ①-7 (the inline-vs-named docs table and the pre-row refusal table are now false). Both are prose on public surfaces (the CHANGELOG-bound changeset and the operator-facing docs page), both are small, and both must be corrected on the head before this narrowing lands. What lifts the verdict: (a) changeset body and PR Implemented-by: VERDICT: FAIL Generated by Claude Code |
Requested by Sales · project thread
Fixes #25491
Clause-②: no (narrowing) — the import doors refuse two request shapes they used to run: an inline mapping entry declaring
transform: 'date_format'(or a transform name outside the declared set), and an entry markedrequired: truewhose column a row leaves blank. Nothing is widened.Metadata-protocol: none (a REST import request's handling; nothing a metadata author writes changes)
What changes
ImportRequestSchema.mappingaccepts aFieldMappingEntry[]whose entries carrytransform,defaultValueandrequired.prepareImportRequest(packages/rest/src/import-prepare.ts) kept only each entry's column and field and dropped the rest, so the import ran with no transform. Each entry's rules now run on its column after the rows are built and before cell coercion, for every format (csv, json rows, xlsx) and for both doors that parse through this function (the sync import and the async import job):trim/uppercase/lowercaserewrite a string cell;noneandlookupcopy it (a lookup cell is still resolved to an id by the runner's reference resolution, as the named mapping'slookupis).defaultValuefills a blank cell (absent, empty, whitespace or one ofnullValues, judged by core'sisBlank, the predicate cell coercion uses).required: truewith a cell still blank refuses the request400 MISSING_REQUIRED_FIELDbefore any row, on the dry run and the commit alike, naming the column and the rows.date_format, and any name outside the declared set, refuses the request400 UNSUPPORTED_TRANSFORMnaming the transform.A plain
{ sourceField, targetField }entry produces no rule and the row is built exactly as before.Measured before choosing the route
The dispatch asked to reuse what the named-mapping path does. Measured on
origin/maindc3e5b9d7b: the named path (applyMappingToRows,packages/core/src/utils/import-mapping.ts) implements a different vocabulary, themappingartifact'snone / constant / map / split / join / lookup(and refusesjavascript).trim,uppercase,lowercaseanddate_formatare implemented nowhere in the tree (git grepoverpackages, tests excluded: the only hit is the spec enum), and no path implements an entry'sdefaultValueorrequired. So there was no shared implementation of these to hand the entries to, and this PR adds no second one: the three string transforms live in one table inimport-prepare.ts;noneandlookupkeep the named path's semantics (copy, resolve downstream); the blank test reuses core'sisBlank. Nopackages/specchange and no file outsidepackages/rest.date_formatis refused rather than applied:FieldMappingEntrycarries no format for it, so it has no defined input. A date or datetime target is already parsed as a date by cell coercion, so the remedy is to drop the transform.Tests
packages/rest/src/import-inline-mapping-entry.test.tsdrives the realPOST /api/v1/data/:object/importroute (real ObjectQL engine, protocol, sqlite:memory:):uppercaseon the credit-code column passes a^[0-9A-Z]{18}$format rule and is stored upper-cased, underwriteMode: upsert/matchFields: ['credit_code'];lowercaseon an email column:ZHOU@X.EXAMPLE.COMthenzhou@x.example.comundermatchFields: ['supplier', 'email']leaves one contact (created 1, then updated 1);trimwithtrimWhitespace: false;defaultValuefor a blank cell and a missing column;requiredrefusal on the dry run and the commit with nothing written;requiredsatisfied by adefaultValue;date_formatrefused400 UNSUPPORTED_TRANSFORM;{ sourceField, targetField }mapping (andtransform: 'none') stores the cells untouched.Ablation (one-shot, through
scripts/ablation-replace.mjs, source-resolved suite so no dist leg): flipping the guardif (entryRules.length > 0) {to a less-than-zero comparison (so the rules never run) turned 5 of 8 red (uppercase, lowercase, trim, defaultValue, required); the 3 that stay green are the parse-timedate_formatrefusal, required-with-default and the control, as expected. Restored: blob equals HEAD,git diff HEADempty.Local verification (head
b5a3141e7f, basedc3e5b9d7b)pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/import-inline-mapping-entry.test.ts: 8/8 passed (lock VERDICT command-exit 0).pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2(whole package): Test Files 322 passed, Tests 6616 passed / 344 skipped (VERDICT command-exit 0). Source unchanged since; the later commits touch only the changeset.pnpm --filter @objectstack/rest build && pnpm --filter @objectstack/rest typecheck(tsc +check:test-typecheck, which compiles the new test file): VERDICT command-exit 0.!title and the(narrowing)arm), onb5a3141e7f:pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood testunder the verify lock: VERDICT command-exit 0, Test Files 290 passed / 3 skipped, Tests 2494 passed / 18 skipped.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran: 67 derived, 65 run (all exit 0), 2 NOT MEASURED:pnpm check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: needs a whole-workspace build) andpnpm check:type-check-debt(exit 3: its closure build waits on the verify lock, which a sibling's dogfood run held past the wait budget). Both run in CI.pnpm lint: NOT MEASURED repo-wide (the verify lock was held by a sibling's 28-minute run past three wait budgets); narrowed and declared:pnpm exec eslint --no-inline-configover the two changed source files, exit 0. CI's Lint & Repo Gates runs the repo-wide sweep.origin/mainmoved 11 commits since the base; none touchespackages/rest,packages/core/src/utilsor the spec's import schema, andgit merge-treeagainst it is clean, so no merge was taken.Acceptance notes
requiredmiss refuses the whole request rather than failing only the offending rows: the runner (packages/core) has no seam for a pre-failed row, and giving it one is outside this card's file surface. The refusal names every row, so the operator fixes the file once.import-mapping.tsdocblock still describes the inline mapping as a plain rename; it is accurate for the compact record form and for plain entries.🤖 Generated with Claude Code
https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX