Repository navigation
content(blog): how do you import a spreadsheet from an old system? import mappings, dry runs, three non-match outcomes (slot 2026-10-25) - #283
Open
objectstack-fleet[bot] wants to merge 2 commits into
Conversation
…mport mappings, dry runs, three non-match outcomes (slot 2026-10-25) Post for #273, slate 1 of the daily cadence (#267), slot Sun 2026-10-25, in all eight locales (zh-Hant generated by `pnpm gen:zh-hant`), with an English and a Chinese cover and two diagrams in each script. Every count in the post was measured on the published @objectstack/cli 17.7.0, on a fresh `npm create objectstack@latest` scaffold carrying the CRM example's Lead object and a `defineMapping` for crm_lead: the dry run reported created 3 with 0 rows stored, the real run stored 3, the same file again updated 3; the same file through an inline rename landed 0 of 3 (INVALID_FIELD on the unmapped Legacy ID column); update mode gave NO_MATCH, a duplicate gave AMBIGUOUS_MATCH, a blank key was created and, under skipBlankMatchKey, BLANK_MATCH_KEY; 5,001 rows answered 413 and 5,000 rows 200; a javascript transform was refused by the build and, saved at runtime, by the import request. Back-links: how-to-move-an-internal-system-to-metadata (en only), extend-existing-systems-with-ai and manufacturing-legacy-systems-ai (all locales) each gain one link sentence and updated: 2026-10-25T10:00:00+08:00. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…dry-run limit without citing the filing Seat review of #273. In three-ways-a-row-fails-to-match (en and zh) two cell texts ran flush to the upsert column's right border — "skipBlankMatchKey: true → skipped" and, in en, "AMBIGUOUS_MATCH, nothing written" — passing the svg-text-fit gate only inside its 2px tolerance. Both are shortened so every cell in both variants keeps at least 20px of clearance. The same scan found the last line of each bottom box in mapping-is-a-projection (en and zh) 7px above its card edge, and one zh record-card line 3px from the right edge: the boxes grow to 178px and the line is shortened. The "dry run of an update" paragraph stays, version-named, as the measured limit writing rule 7 asks for. The sentence that referred to the measurement being with the ObjectStack team is removed from every locale: the seat files the upstream finding, and the post does not cite it. zh-Hant regenerated with pnpm gen:zh-hant. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #273
Slot: Sun 2026-10-25, on the shared calendar on #267. Merge on the slot day only. Seat review. The seat found one visual defect: two cells in the three-ways diagram ran flush to their card edge. It was fixed in
3a2a799, which also fixed three more tight boxes found by a clearance scan. The seat re-viewed the diagram at native size. The measured "dry run of an update" limit stays in the post (writing rule 7). The upstream defect is filed separately in objectstack, and the post does not cite it. ACCEPT.Slot Sun 2026-10-25 (
date: 2026-10-25in every locale, per the coordinator's calendar change during the run; the card said Tue 2026-10-27). Merge on the slot day only.What this adds
content/blog/how-to-import-a-spreadsheet-from-an-old-system/— the post in all eight locales (index.mdx,index.zh-Hans.mdx,index.zh-Hant.mdxgenerated bypnpm gen:zh-hant,index.ja.mdx,index.de.mdx,index.es.mdx,index.fr.mdx,index.ko.mdx), a cover and two diagrams, each as an English-text asset (cover-en.svg,mapping-is-a-projection-en.svg,three-ways-a-row-fails-to-match-en.svg) and a Chinese-text pair (cover.svg,mapping-is-a-projection.svg,three-ways-a-row-fails-to-match.svg). Topicintegration-data, audiencebusiness, solutioncrm.created 3from the dry run while the table holds 0 rows, 3 after the real run,updated 3on the second run.updated: 2026-10-25T10:00:00+08:00each:how-to-move-an-internal-system-to-metadata(English only, as published),extend-existing-systems-with-aiandmanufacturing-legacy-systems-ai(all seven authored locales; zh-Hant regenerated).Proof artifact — measured on the published
@objectstack/cli17.7.0A fresh
npm create objectstack@latest crmscaffold (Create ObjectStack v17.7.0;@objectstack/cli@17.7.0,@objectstack/spec@17.7.0,@objectstack/runtime@17.7.0; npmlatest= 17.7.0) carrying the CRM example'sLeadobject fromexamples/app-crm/src/objects/lead.object.tsat tag@objectstack/cli@17.7.0(its two lookups tocrm_account/crm_opportunityleft out, said in the post) and adefineMappingforcrm_leadregistered throughmappings:. Serverobjectstack dev --no-watch -p 49180, dev admin session, every callPOST /api/v1/data/crm_lead/import. Logs underscratchpad/issue-273/proof/(run.log,run-dryrun-update.log,run-projection.log,run-413.log,run-saved.log,build-javascript.log); the app itself underscratchpad/issue-273/app/crm/.mappingName: "crm_lead_feed", dryRun: true, 3 rowstotal 3, ok 3, errors 0, created 3, updated 0, skipped 0dryRuncreated 3, anidon every row;sourcestored asweb,partner,webcreated 0, updated 3writeMode: "update", 2 rows (one known, one new)updated 1, skipped 1; row 2skipped,code: "NO_MATCH"Trade Show/ labelEvent/not-an-email/ blank emailfailed,field: "source",code: "invalid_option", "Lead Source: "Trade Show" is not a known option"; row 2created(sourceevent); row 3failed,field: "email",code: "invalid_email"; row 4created;ok 2, errors 2skipBlankMatchKey: true(dry run)skipped,code: "BLANK_MATCH_KEY"writeMode: "insert", then upsertfailed,code: "AMBIGUOUS_MATCH", "matchFields matched more than one crm_lead record"Legacy IDcolumn): inlinemappingdry run / real run;mappingNamedry runfailed,code: "INVALID_FIELD",field: "Legacy ID", "Unknown field 'Legacy ID' on object 'crm_lead'" (dry run and real run alike); artifact: 3 of 3createdmappingfailed,code: "invalid_option",field: "source", "Lead Source: "Webform" is not a known option"413 PAYLOAD_TOO_LARGE, "Import limit is 5000 rows per request (got 5001). Use an async import job for larger files." /200,total 5000, ok 5000, created 5000PUT /api/v1/meta/mapping/crm_lead_feed_saved(same shape, new name); dry run by that name;PUTto the packaged name200, "Saved mapping 'crm_lead_feed_saved' (env-wide, state=active)";created 3;403 NOT_OVERRIDABLEjavascripttransform, build timetransform: 'javascript',objectstack buildjavascripttransform, runtime-savedPUT .../crm_lead_feed_jswith ajavascriptentry, then import by name200; import400 UNSUPPORTED_TRANSFORMmappingNameand inlinemapping/ unknown name400 CONFLICTING_MAPPING, "Provide either mappingName or an inline mapping, not both" /404 MAPPING_NOT_FOUND, "No mapping artifact named "crm_lead_feed_nope" is registered"Two things the card predicted came out differently and the post says what was measured: (1) a row with an unknown
Channelcode passes through themapunchanged, as documented, and then fails at select coercion withinvalid_optionnamingsource— it is not a row that lands unchanged; (2) the strict-projection contrast could not be shown on the card's 3-row file, because the inline route fails that file one step earlier (untranslated codes), so a second file was built whose only difference from a clean one is the junk column.Every factual claim and its source line
objectstack at tag
@objectstack/cli@17.7.0(ba459d8d5);content/docs/data-modeling/import-mappings.mdxis byte-identical at the tag and at localorigin/main(diff empty).import-mappings.mdx:7-8POST /api/v1/data/:object/importapplies it by name withmappingName:8-10:12-15defineMappingshape (name,label,sourceFormat,targetObject,fieldMapping[],transform: 'map'+valueMap,mode: 'upsert',upsertKey: ['email']); registered withmappings: [...]:22-43,:48-53; the post's own mapping, compiled and served (dist/objectstack.json,GET /api/v1/meta/mapping/crm_lead_feedanswers it with_provenance: "package")defineStack()validates while you build: an undefinedtargetObjectfails the build; ajavascripttransform fails the build:56-61; measured exit 2 (above)defineMapping()parses at import time; a misspelled key is rejected with the canonical spelling named:63-68PUT /api/v1/meta/mapping/NAME); saved rows read first, packaged registry as fallback; packaged mappings locked against tenant edits; no per-organization overlay:74-86; measured200saved under a new name and403 NOT_OVERRIDABLEunder the packaged namemodesupplieswriteModewhen omitted;upsertKeysuppliesmatchFields:96-100,:181-184TransformTypevalues; "A value with no entry in the table passes through unchanged";lookuppass-through;javascript"Refused. There is no server-side sandbox, and silently skipping a declared transform would corrupt data":118-129;packages/core/src/utils/import-mapping.ts:221-226(raw in valueMap ? valueMap[raw] : raw):131-132object, dryRun, writeMode, total, ok, errors, created, updated, skipped, results[]):136-169;packages/rest/src/rest-server.ts:9347-9358"dryRun": truereturns "the same report with nothing persisted; the verdict comes from the engine's own write-path validation":176-177;packages/spec/src/api/export.zod.ts:274-282runAutomationsis a request key (named only, not described):186-187mappingvsmappingNamemutually exclusive,400 CONFLICTING_MAPPING; unmapped columns "pass through to the write path under their own header" vs "Dropped. The artifact is a strict projection: only mapped targets survive":195-203;packages/core/src/utils/import-mapping.ts:151-152("Returns NEW rows containing only mapped targets")MAPPING_NOT_FOUND, 400MAPPING_TARGET_MISMATCH, 400UNSUPPORTED_TRANSFORM, 400CONFLICTING_MAPPING, 413PAYLOAD_TOO_LARGEover 5,000 rows):215-224;packages/rest/src/import-mapping.ts:63-96; measured 404 / 400 / 413created,updated,skipped NO_MATCH,skipped BLANK_MATCH_KEY,failed AMBIGUOUS_MATCH,failedwith a field code); "A row that fails does not stop the import"; "a row that does not match is not one behaviour but three":228-244;packages/core/src/utils/import-runner.ts:962-986:254-265POST /api/v1/data/:object/import/jobs, job id, poll, cancel/undo:271-274; measured 5,001 → 413, 5,000 → 200sourceoptionsweb,referral,event,social,cold_outreach,partner;name,email,companyfieldsexamples/app-crm/src/objects/lead.object.ts:19-40, 57-67at the tagmappings:today (the post adds one)examples/app-crm/objectstack.config.ts:37-120at the tagnpm create objectstack@latestinstalls 17.7.0npm view @objectstack/cli dist-tags→latest: 17.7.0; scaffold banner "Create ObjectStack v17.7.0"src/lib/positioning.ts(T4_STANDALONE,CANONICAL.e1, and the zh-Hans / ja / de / es / fr / ko strings)defineMapping(so the CTA points at the docs page)git grep defineMapping '@objectstack/cli@17.7.0' -- 'skills/**'→ no hitsA measured limit the post states (and a finding for the ObjectStack team)
On 17.7.0, a dry run of rows that resolve to an update failed every row with
code: "rule_violation"on the Lead object'slead_score_rangescript rule — "Validation rule 'lead_score_range' could not be evaluated (runtime: No such key: lead_score) — write rejected. The predicate reads 'lead_score', which this object does not declare" — while the real run of the same body answeredupdated 3, and a directPATCHof one record with the same four fields answered 200. The object does declarelead_score; the preview evaluated the rule against the incoming cells alone. Previews of rows that are created agreed with the write in every run. The post keeps this as a version-named paragraph in "What this does not solve" (seat ruling A); the post does not cite the filing. Evidence:proof/run-dryrun-update.logA–D; server log lines "Validation rule 'lead_score_range' predicate failed to evaluate (runtime: No such key: lead_score". Contract it breaches:packages/core/src/utils/import-runner.ts:28-40at the tag ("A dry run's contract is that it reports the verdict the real write produces"). Filed through the seat, not by this PR.Gates (final tree, tip
3a2a799; two commits onlin/issue-273:bfc0687the post,3a2a799the seat-review fix)pnpm content:lintscratchpad/issue-273/clint-5.logpnpm content:lint --publishedbuild-5.logline 71pnpm svg:lintsvglint-final-2.log,build-5.logline 72pnpm check(viaos-verify-lock.sh)VERDICT command-exit 0— 160 files, 0 errors / 0 warnings / 0 hintscheck-final-3.logpnpm build(viaos-verify-lock.sh)VERDICT command-exit 0;content lint --dist passed (494 built blog pages); CJK line breaks, quotation marks and links passed;git statusclean afterwardsbuild-5.logpnpm seo:smokeseo-4.logAll descriptions ≤ 230 characters (en 224, zh-Hans 97, ja 121, de 228, es 221, fr 227, ko 133).
Browser check (actually looked at)
astro previewon 127.0.0.1:49181, Chromium via Playwright, scriptscratchpad/issue-273/shots.mjs, screenshots underscratchpad/www/shots-273/(123 files,results.json). 265/265 assertions pass (shots-4.log, on thedist/of3a2a799): the article in en and zh-Hans at 1366×625, 1440×750, 1512×830, 1920×945, 390×844 and 360×800, light and dark, lazy images scrolled into view first — HTTP 200, no horizontal overflow outside a scroll container, no repeated image, the cover renders, every image loads; the blog list in en and zh-Hans at 1440 and 390, light and dark, links the new post; the three back-linked posts show the new link and an Updated line; every new SVG at native size on a light and a dark page, lowest text contrast 4.59:1 (first pass found 4.3:1 on the muted grey over the cream cards; lifted to 5.5:1+). Looked at: both covers light/dark, both diagrams light/dark, the article at 1440 light, 1920 dark, 390 dark, 360 light, the zh-Hans article at 1366, phone-width crops of a code block and the tables (horizontal scroll containers), the list page.Deviations and notes for the seat
Seat-review fix (
3a2a799): inthree-ways-a-row-fails-to-match(en and zh) two cell texts ran flush to the upsert column’s right border inside the gate’s 2px tolerance; shortened toskipBlankMatchKey → skippedandAMBIGUOUS_MATCH, not written. A verbose clearance scan of all six SVGs then also lifted the last line of each bottom box inmapping-is-a-projection(7px from the card edge; boxes grown to 178px) and shortened one zh record-card line (3px from the right edge). The sentence referring to the measurement being with the ObjectStack team is removed from every locale.Slot changed mid-run by the coordinator from 2026-10-27 to 2026-10-25; applied to every
dateandupdated. The Publishing cadence: one post a day, switched on once seven approved posts are queued (maintainer ruling B, 2026-10-17) — charter change, shared calendar, writing capacity #267 calendar table still lists Post 4 (Post (slot Tue 2026-10-20): Agent-Operable Apps: When Every Object and Action Is an MCP Tool Under the User's Permissions #230, PR content(blog): agent-operable apps — every object and action an MCP tool under the user's permissions (post 4, slot 2026-10-20) #237) on Tue 10-27 and nothing on 10-25; the seat owns that table.The non-English locales link the English-only
how-to-move-an-internal-system-to-metadataat/en/…(the pattern the reference post uses; the content-lint cross-locale rule accepts it). de/es/fr/ja/ko link the glossary term at/en/glossary/typed-metadata/becausecontent/glossary/{de,es,fr,ja,ko}/typed-metadata.tsdo not exist and the same-locale URL renders a noindexed fallback (lint warns on it).de/es/fr: the two quoted English server messages are wrapped in a span with
lang=enso the SEO language check reads them as English (the first smoke run flagged the paragraph).ja and zh-Hans: a bold that ended on a full stop before a CJK letter rendered as literal asterisks (
content-lint --dist); the full stop moved outside the bold.Commit trailers are the two lines the dispatcher asked for (
Co-Authored-By: Claude,Claude-Session: …), not the harness reminder's model-named form.The worktree
/home/user/www-issue-273is kept with itsnode_modulesfor the seat to pushlin/issue-273; nothing was pushed, no PR opened, nothing posted to GitHub (fourgh apireads only: issue Post (slot Sun 2026-10-25): How Do You Import a Spreadsheet From an Old System Into an AI-Built App? Import Mappings, Dry Runs, and the Three Ways a Row Can Fail to Match #273, its comments, open PRs, issue Publishing cadence: one post a day, switched on once seven approved posts are queued (maintainer ruling B, 2026-10-17) — charter change, shared calendar, writing capacity #267).Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr