Skip to content

feat(core): import reads dotted and compact year-first days and matches option values in any case - #25596

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-25582-import-cell-shapes
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-25582-import-cell-shapes

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25582

Clause-②: yes (widening: two date-cell spellings and an option value in another case, each refused before with invalid_date / invalid_option, are now read; nothing read before is refused or stored differently)
Metadata-protocol: none

What

Ruling 6377844879 (on #25493, letter A), proposals 1 and 2, in packages/core/src/utils/import-coerce.ts:

  1. parseDateCell reads YYYY.MM.DD and YYYYMMDD. A four-digit year, a two-digit month and day, nothing else in the cell. 2011.07.01 is stored as 2011-07-01, 20090615 as 2009-06-15. The day must exist (namesRealCalendarDay, the same arithmetic as the other shapes): 20090230 and 2009.02.30 stay invalid_date. No other form is widened: 2011.7.1, 2011.07.01 9:00, 11.07.01, 01.07.2011, 2011.07-01, 200961, 2009615, 200906150 and 20090615 9:00 stay refused.
    • Field kinds. Read for a date and a datetime (midnight UTC, the reading a bare 2026/7/15 already gets on a datetime), never for a time: both spellings name a day and no clock. The time branch keeps its year-first-only fallback unchanged.
    • A number is not a compact day. The compact form is read only from a text cell, so a JSON number 20090615 stays refused. The existing reader treats numbers as never temporal (outsideYears docblock: an Excel serial, epoch ms). An xlsx cell holding that number reaches the reader as its text (xlsxCellToString in import-prepare.ts), so it is read. That path is read from the code, not measured.
    • An eight-digit cell on a number or text column is unaffected (20090615 → the number 20090615 / the text '20090615'). This is pinned.
  2. Option values match case-insensitively. matchOption now has three passes: exact value, then label case-insensitively (as before), then value case-insensitively. It stores the declared value, never the cell's spelling: CN stores cn. The value pass runs last, so a cell that matched before stores what it stored (pinned: a cell that is one option's label and, by case, another option's value keeps the label match). Single select, multiple: true select and multiselect parts all go through this one function.
    • Ambiguity, measured. The spec cannot declare two values that differ only by case: FieldSchema.safeParse on [{value:'cn'},{value:'CN'}] fails at options.1.value ("System identifier must be lowercase…"). A programmatic engine.registry.registerObject accepts and keeps the pair unparsed. Measured with a throwaway test against @objectstack/objectql, then deleted. So the pass refuses to guess. A cell naming two such values by case alone (Cn) gets the no-match answer it got before (invalid_option, or kept under createMissingOptions). An exact cn / CN still matches exactly. The same value declared twice counts as one value.

#25582 remains open: proposal 4 (every cell error of a row) is not in this PR. It needs an errors member on ImportRowResultSchema first (the domain:spec seat's; the note is on the card), and import-runner.ts still keeps errors[0]. Proposal 3 (aliases) was not ruled in.

Tests

  • packages/core/src/utils/import-coerce.test.ts: a [#25582] block covering the new forms on date and datetime, the invalid-calendar controls, the not-widened controls, time refusal, number refusal, the 8-digit number/text control, and the row-level invalid_date. matchOption gets value-case match storing the canonical value, label-ahead-of-value, and the ambiguous pair. coerceRow gets select, multiselect and multiple: true select parts stored canonical, with keptOptionValues empty and an unmatched part named.
  • packages/rest/src/import-year-first-day-option-case.test.ts (new): through the real POST /api/v1/data/:object/import over SqlDriver (better-sqlite3), a CSV row 2011.07.01,20090615,CN imports and stores 2011-07-01 / 2009-06-15 / cn. The JSON dry run of the same row is clean and persists nothing. 20090230 and DE are still that row's invalid_date / invalid_option.

Ablation, once per proposal. Each leg went through scripts/ablation-replace.mjs (anchor hit 1 → 0, blob moved). Then @objectstack/core was rebuilt, because the rest test reads core's dist/, and ablation-dist-preflight.mjs saw the marker in dist/index.js and dist/index.cjs.

  • P1 (the readYearFirstDay call removed): core 9 failed / 167 passed of 176. Route 3 failed of 3 (ok: 0, errors: 1 on the ledger row).
  • P2 (the value pass returns nothing): core 3 failed / 173 passed. Route 2 failed / 1 passed (the refusal case stays green, as expected).
  • Restore. Blob == HEAD (60af90a93041), git diff HEAD empty. Core rebuilt, and --absent passed for both markers on all 14 built files with a clean tree.

Gates

All readings below were taken at head c630eacca0 (after merging origin/main), unless a line names another commit.

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 70 commands. All 70 ran and exited 0. --ran gives: ✓ dispatch-gates --ran: 70 derived famil(ies) accounted for — 70 run, 0 NOT-MEASURED.
  • pnpm lint, through the verify lock: VERDICT command-exit 0.
  • pnpm --filter @objectstack/core test: 118 files / 2757 tests passed. pnpm --filter @objectstack/core typecheck and pnpm --filter @objectstack/rest typecheck passed (each package's test-layer check:test-typecheck: OK). Both through the lock, VERDICT command-exit 0.
  • pnpm --filter @objectstack/rest exec vitest run over the new file plus import-date-cell-iso-real-day, import-dryrun-parity and import-integration: 4 files / 119 tests passed.
  • After the merge: the @objectstack/rest closure was rebuilt, and pnpm --filter @objectstack/spec check:generated gives ✓ All 14 generated artifacts are up to date.
  • NOT MEASURED locally: the whole @objectstack/rest suite (only a test file is added there; CI Test Core runs it) and test:repo (no repo-project test is touched).

Acceptance notes

  • The xlsx template's instructions sheet (TEMPLATE_READER_CLAIMS in packages/rest/src/import-template.ts) lists example date spellings and does not mention the two new ones. Every claim it makes is still true (none of its refused examples is now read). Adding them is outside this slice's file surface. carrier: none.
  • 2011.7.1 (dotted, one-digit month/day) stays refused, per the ruling's 「不放宽其它形式」. Widening it would be a new decision.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX

…hes option values case-insensitively

Part of the ruling's letter A (proposals 1 and 2).

Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX
Co-authored-by: Claude <noreply@anthropic.com>
…alue through the import route

Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e073455929a60b67c48da3f91324261e57aaa26b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 88bc2fef2425bc0cc51a46b74aac2ef9af1212e6 — the merge of head c630eacca0a73660a2d582895d95ffd2321e320d into base e073455929a60b67c48da3f91324261e57aaa26b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 88bc2fef2425bc0cc51a46b74aac2ef9af1212e6 && git checkout 88bc2fef2425bc0cc51a46b74aac2ef9af1212e6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e073455929a60b67c48da3f91324261e57aaa26b c630eacca0a73660a2d582895d95ffd2321e320d && git checkout -B drift-repro e073455929a60b67c48da3f91324261e57aaa26b && git merge --no-ff c630eacca0a73660a2d582895d95ffd2321e320d

node scripts/docs-audit/affected-docs.mjs --json e073455929a60b67c48da3f91324261e57aaa26b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 24, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:cli seat 1 (seat post #6024; lane read as domain:server) · os-sales · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-24T03:55Z · head c630eacca0

item reading
Shape 4 files, +319 / −7 (diff read by the seat; production source import-coerce.ts +69 / −7). Ruling 6377844879 letter A proposals 1 and 2 only. (1) parseDateCell reads YYYY.MM.DD and, from a text cell only, YYYYMMDD. Both forms need a four-digit year, two-digit month and day, and a real calendar day. They apply to date and datetime (midnight UTC, as a bare 2026/7/15 already is), never to time. (2) matchOption gains a third pass after the exact value and the label: the value matched case-insensitively, storing the declared value. Two declared values that differ only by case are not guessed between. Part of #25582: proposal 4 waits on a spec member (note on the card).
Read against the code Precedence in parseDateCell: kind === 'time' ? undefined : (iso ?? yearFirstDay), then ?? readYearFirstCell, so a time cell never reads the new forms and an ISO cell is read as before. The value pass runs last, so a cell matched before stores what it stored. matchOption has no caller outside import-coerce.ts. A JSON number 20090615 stays refused (a number is never a temporal cell); an xlsx numeric cell arrives as text and is read (from the code, not measured).
Tests Core unit pins for each form, the invalid-day controls (20090230, 2009.02.30), the not-widened forms (2011.7.1, a clock, a two-digit year, day-first, 6/7 digits), the non-date column control, the canonical-value store, multiselect parts and the case-only ambiguity. New route pin through the real POST /api/v1/data/:object/import. Ablation per proposal: P1 9 core + 3 route red, P2 3 core + 2 route red; both restored to HEAD and green.
Clause-② / contract review The claim's no is revised on the measured diff to yes (widening: …), the arm AGENTS.md's Post-Task Checklist defines for an accept set that only grows. Metadata-protocol: none, @objectstack/core patch, no ADR-0087 marker (not breaking; the registration gate saw it as non-breaking). No ! and no (narrowing) arm, so neither a contract review nor the dogfood battery is owed.
Gates (check-runs on this head) 41 runs: 34 success, 7 skipped, 0 failed. mergeable_state: clean. Local: dispatch-gates 70 derived, 70 run exit 0 on the final head; pnpm lint exit 0. The full @objectstack/rest suite was not run locally (only a test file was added there); CI Test Core is green. Report: mcp_calls 0; writes all through the relay.
Out of scope (noted, not filed) The xlsx template's reader instructions (import-template.ts TEMPLATE_READER_CLAIMS) do not mention the two new date spellings. Every claim they make stays true; recorded in the PR's Acceptance notes.
Landing Not governed (check-governed-merges.mjs --pr 25596, +319 / −7). Ready + auto-merge through the relay in this act. The card stays open for proposal 4.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 24, 2026 03:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 24, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as draft October 24, 2026 03:58
Merged via the queue into main with commit 9f39094 Oct 24, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25582-import-cell-shapes branch October 24, 2026 04:32
marchtian pushed a commit that referenced this pull request Oct 24, 2026
This reverts commit 74fa3cb.

The 18.1.0 publish was refused at that version commit: its full battery is red
(Dogfood Regression Gate, the showcase D3/D4 proof pinning the pre-#25549
refusal sentence), and the release gate reads only runs at or before the
version commit, so no re-run on it can go green. Reverting restores the 265
changesets it consumed, including the five that landed after the Version
Packages PR was generated (#25591, #25592, #25596 and two patches), so the
regenerated Version Packages PR versions 18.1.0 again from a tree whose
battery can be read green, with a changelog that names everything it ships.
Nothing was published: npm still has 18.0.0 and no 18.1.0 tag exists.

Claude-Session: https://claude.ai/code/session_014C7q5Hm34g4EPdirar9SzN
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants