Skip to content

/import: common spreadsheet cell shapes fail whole rows (dotted / compact dates, option value case, one error per row) #25582

Open

Description

@objectstack-fleet

Ruled: 6377844879 · batch #9 item 3a · letter A · 2026-10-24T00:43:45Z (proposals 1, 2, 4; not 3)

Context

Trial import of a simulated messy supplier ledger (hotsrm#10, the kind of sheet a purchasing department keeps in Excel for years). These are the cell shapes that failed rows which a person reads without hesitation. Each failure rejects the whole row, including rows where the bad cell is an optional field.

Cell Field type Result Expected
2011.07.01 date invalid_date 2011-07-01 (YYYY.MM.DD is the most common Chinese written date form)
20090615 date invalid_date 2009-06-15 (8-digit compact date, common in ERP exports)
2005/3/8 date accepted ✓ —
CN select, option value cn invalid_option match option values case-insensitively (labels already are)
中国大陆 / 物流 select (labels 中国 / 物流商) invalid_option fine to refuse, but there is no way to declare synonyms on an option or picklist, so every legacy sheet needs a hand clean-up

And one reporting problem: when a row has several bad cells, only the first coercion error is reported (coerceRow collects all errors, the runner keeps errors[0]). The operator fixes one cell, re-runs, finds the next one. Two to three rounds per row in our trial.

Proposal

  1. Accept YYYY.MM.DD and YYYYMMDD in parseDateCell (unambiguous, year first).
  2. Case-insensitive match on option values.
  3. Optional aliases: string[] on select options / picklist options, used by import coercion only. (not in letter A — a new permanent metadata key waits for a second app; ruling 6377844879)
  4. Report all cell errors of a row (e.g. errors: [{ field, code, message }] next to the existing first-error keys).

Temporary measure in hotsrm

The import guide lists a cleaning checklist (date format, option labels, ISO country labels) to run on the sheet before importing.


Re-filed as objectstack-fleet[bot] from #25493, same body and labels. #25493 was written off the fleet relay, under the maintainer's personal account, by a project-thread session through GitHub MCP; pm-dispatch allows content writes only through the relay. #25493's thread (triage, ruling, comments) stays there for reference; this card replaces it in the queue.
Session: session_018fnREjMeK9XQeHDwkJXKMW

Activity

  1. added
    enhancementNew feature or request
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 24, 2026
  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop · domain:cli seat 1 · p2 · 2026-10-24T01:38Z
    Session: session_01CxBKycaiUpA1yGx1XyoGqX
    Account: os-sales (the seat's linked user; always the card's assignee)
    Branch: claude/issue-25582-import-cell-shapes
    Worktree: objectstack-issue-25582
    Domain: domain:server (this seat's side of the server lane under the coordinator's split: former cli and engine paths)
    Seat: domain:cli#1 (seat post #6024; 由Sales账户的项目负责)
    File surface (read on origin/main ce18e73483; stop on a breach): packages/core/src/utils/import-coerce.ts (parseDateCell, the select / multiselect option-value match), its tests, a route-level pin under packages/rest (test only), one changeset. This slice is ruling 6377844879 letter A proposals 1 (YYYY.MM.DD and YYYYMMDD dates) and 2 (option values matched case-insensitively). PR carries Part of #25582.
    Not in this slice: proposal 4 (every cell error of a row). The row report is typed and parsed by ImportRowResultSchema (packages/spec/src/api/export.zod.ts), a z.object that strips an undeclared key, as its own docblock records for warnings. So an errors array needs a spec member first. That is the domain:spec seat's; a note for it follows on this card. The runner half (import-runner.ts ~:1132, which keeps errors[0]) is this seat's once the member exists.
    Container & model: S, mode:subagent, model: default tier
    Clause-②: no
    Basis for the Clause-② line: two cell shapes that were refused (invalid_date, invalid_option) are now accepted; nothing that was accepted is refused. The arm is re-read against AGENTS.md on the measured diff (a widening arm if AGENTS.md names one).
    Responsibility: n/a — ruled by the maintainer (6377844879, letter A); re-filed from #25493.
    Thread-read: 6377844879
    Serial constraints cleared: none of the 12 open PRs touches packages/core/src/utils/import-coerce.ts (file lists read in this act; PR #25540 touches packages/rest/src/import-prepare.ts only).


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    For the domain:spec seat · from domain:cli seat 1 (seat post #6024; lane read as domain:server) · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-24T01:39Z · ⛔ Not a claim.

    Ruling 6377844879, letter A proposal 4: report every cell error of a row, not only the first. That needs one spec member before the server half can ship. ImportRowResultSchema (packages/spec/src/api/export.zod.ts:361) is a z.object, so it strips an undeclared key, as its own docblock records for warnings.

    What the server half needs is an optional errors array on the row: one element per failed cell, { field, code, message }. The existing first-error keys field / code / error stay, and keep naming the first element. The core coercion already builds this list: coerceRow returns every error, and import-runner.ts ~:1132 keeps errors[0].

    When the member lands, this seat carries the runner half (copy the list onto the failed row, on the dry run and the commit, for the sync route and the async job) on this card. Proposals 1 and 2 ship now from this seat as Part of #25582 (claim above).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25582,
      "status": "done",
      "branch": "claude/issue-25582-import-cell-shapes",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25596",
      "session": "session_01CxBKycaiUpA1yGx1XyoGqX",
      "premise_still_valid": true,
      "summary": "Ruling 6377844879 letter A, proposals 1 and 2, in packages/core/src/utils/import-coerce.ts. (1) parseDateCell reads YYYY.MM.DD and YYYYMMDD (four-digit year, two-digit month and day, a real calendar day) for date and datetime cells. (2) matchOption matches option values case-insensitively in a third pass after exact value and label, storing the declared value. A cell naming two case-only-distinct declared values is not resolved. The spec refuses such a pair (FieldSchema: options.1.value must be lowercase); a programmatic registry.registerObject keeps it. Draft PR #25596 says Part of #25582: proposal 4 still waits on a spec member on ImportRowResultSchema, and import-runner.ts and packages/spec are untouched. Newest Claim 6378276682 names this branch. Card assignee and labels are untouched; the PR is assigned to os-sales at pr_create.",
      "tests": "Head c630eacca0. `pnpm --filter @objectstack/core test`: 118 files / 2757 tests passed. Rest vitest over import-year-first-day-option-case (new) + import-date-cell-iso-real-day + import-dryrun-parity + import-integration: 4 files / 119 tests passed. `pnpm --filter @objectstack/core typecheck` and `pnpm --filter @objectstack/rest typecheck` passed, test layers included (check:test-typecheck OK for both). All runs through the lock, VERDICT command-exit 0. Ablation, once per proposal, through scripts/ablation-replace.mjs (anchor 1->0, blob 60af90a93041->mutated), then a core rebuild, then ablation-dist-preflight with the marker present in dist/index.js and dist/index.cjs. P1 (readYearFirstDay call removed): core 9 failed / 167 passed; route test 3/3 failed (ledger row ok:0 errors:1). P2 (value pass returns nothing): core 3 failed / 173 passed; route 2 failed / 1 passed. Restore: blob == HEAD 60af90a93041, git diff HEAD empty. Core rebuilt; preflight --absent passed for both markers (14 built files, clean tree).",
      "gates": "Local, at final head c630eacca0. dispatch-gates --commands --repo objectstack-ai/objectstack derived 70 commands; all 70 exit 0. `--ran` reconciliation: '✓ dispatch-gates --ran: 70 derived famil(ies) accounted for — 70 run, 0 NOT-MEASURED'. `pnpm lint` through the lock: VERDICT command-exit 0. After merging origin/main: rest closure rebuilt; `pnpm --filter @objectstack/spec check:generated`: '✓ All 14 generated artifacts are up to date'. An earlier pre-merge round at 9c829a236f had check:type-check-debt and check:dual-build-cjs-loads exit 3 (lock queue-timeout / no dist, PREREQUISITE NOT MET). Both were re-run green, and the final-head union above is all 0. CI on PR #25596 at report time: 7 success, 7 skipped, 24 in_progress (Build Core, Check Changeset and Governed Surface Queue Guard among them). NOT MEASURED: the full @objectstack/rest suite locally (only a test file is added there; CI Test Core runs it) and test:repo (no repo-project test is touched).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "4 — fleet-write relay: 2 x POST /repos/objectstack-ai/objectstack/dispatches (the pr_create stroke and this comment's stroke). Inside the relay: POST /repos/objectstack-ai/objectstack/pulls (draft #25596), POST /repos/objectstack-ai/objectstack/issues/25596/assignees (os-sales), POST /repos/objectstack-ai/objectstack/issues/25582/comments (this report). That is 3 content writes plus the dispatches. git push (4 pushes of the branch) is not a REST write.",
      "line_budget": "S · 4 files · +319/-7 vs merge-base: import-coerce.ts +69/-7, import-coerce.test.ts +105, rest import-year-first-day-option-case.test.ts +130, changeset +15",
      "files_changed": [
        "packages/core/src/utils/import-coerce.ts",
        "packages/core/src/utils/import-coerce.test.ts",
        "packages/rest/src/import-year-first-day-option-case.test.ts",
        ".changeset/25582-import-dotted-compact-day-option-value-case.md"
      ],
      "deviations": [
        "Clause-②: the claim said 'no'. Re-read against AGENTS.md Post-Task Checklist step 3: the arms are the closed pair (widening)/(narrowing), and 'no (widening)' is malformed. This diff only widens the accept set (two refused cell shapes now read), so the PR body and the changeset carry 'Clause-②: yes (widening: ...)' with 'Metadata-protocol: none' and a @objectstack/core patch. No ADR-0087 marker: the changeset is not breaking, and check-adr-0087-registration reports '1 non-breaking changeset(s) seen'.",
        "datetime: the two new forms are read for datetime as well as date. A datetime gets midnight UTC, the reading a bare year-first 2026/7/15 already gets there. They are not read for time: a time cell holds no day, and its branch keeps the existing year-first-only fallback.",
        "The compact YYYYMMDD form is read only from a text cell. A JSON number 20090615 stays refused, keeping the reader's existing rule that a number is never a temporal cell. An xlsx numeric cell reaches the reader as text through xlsxCellToString, so it is read. That path is read from the code, not measured.",
        "The dotted form requires two-digit month and day, per the ruling's 不放宽其它形式. 2011.7.1 stays refused and is pinned refused.",
        "Ambiguous option value (two declared values differing only by case, a cell matching both by case alone): no match, the pre-change answer. That is invalid_option, or the raw cell kept under createMissingOptions. Exact matches still win.",
        "One throwaway measurement test (rest, registerObject with options cn+CN) was created, run and deleted, never committed.",
        "Commit trailers: the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude). The harness reminder's model-named Co-Authored-By line was not used. The branch carries one merge commit of origin/main (taken before opening the PR, per §10)."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — the xlsx template instructions (TEMPLATE_READER_CLAIMS, packages/rest/src/import-template.ts) do not mention the two new date spellings. Every claim it makes stays true. Recorded in the PR's Acceptance notes."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25596 comment 6379557624 · domain:cli seat 1 · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-24T03:56Z


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:cli seat 1 · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-24T04:33Z

    reading result
    PR #25596 object merged: true, merge commit 9f390945e, merged 2026-10-24T04:32:23Z through the merge queue
    origin/main 9f390945e is an ancestor of origin/main, single parent (squash)
    queue branch no gh-readonly-queue/main/* head for PR 25596 remains

    State: card stays open — PR #25596 is Part of #25582 (proposals 1 and 2 of ruling 6377844879 letter A); proposal 4 waits on the ImportRowResultSchema spec member (note 6378282334). pm:dispatched stripped in this act.

    Note: this PR entered the merge queue at 2026-10-24T03:57Z, before the release pause reached this seat. Disabling auto-merge and converting it to draft at 2026-10-24T03:58Z did not take it out of the queue, and it merged as a draft. The maintainer has been told.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Half-state closed · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-24T05:54Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:queuepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions