Skip to content

清单导入走平台标准 Import:提供样例 CSV,浏览器走通目录项与职责的导入并留档 #19

Description

@os-warren

从 post-v1 提到 M2,并改范围。 维护者决定:导入用平台标准接口,不做定制。控制台每个对象列表上已经有「Import」按钮(平台能力)。本卡不写导入代码;做的是让演示时「导入现有清单」这一步真的能走通、有据可查。

做什么

  1. samples/ 目录放两份 CSV:catalog-items.csv(岗位职责目录,≥15 行,覆盖 recurring/standing/one_off、各频次、宽限期)和 duties.csv(≥10 行,含 owner、business_unit 的自然键写法)。用种子数据同一家虚构公司,别再造第二套。
  2. 在浏览器里用平台的 Import 把两份 CSV 导进一个干净库(pnpm dev,不是 pnpm demo),截图每一步:选文件 → 字段映射 → 结果。如实记录平台导入对 lookup 自然键(owner 按 sys_user.name、business_unit 按名字)的解析行为——能解析就写清楚格式要求;不能就上报平台并在 README 写变通。
  3. README「导入现有清单」一节:三步、字段要求、样例链接。
  4. 一条测试:两份 CSV 的表头 ⊆ 对应对象的可导入字段(按 schema 读,不手抄),防止对象改了字段样例悄悄失效。

不做

  • 不写任何导入 handler / action / job。
  • 不做 Excel 解析。CSV 是平台接口收的格式。

验收

  • 截图证明两份样例通过平台 Import 落库,行数一致
  • README 有那一节;测试守卫在
  • 四道门绿

Activity

  1. changed the title [-]Spreadsheet import for an existing duty catalog[/-] [+]清单导入走平台标准 Import:提供样例 CSV,浏览器走通目录项与职责的导入并留档[/+] on Sep 2, 2026
  2. self-assigned this
    on Sep 2, 2026
  3. os-warren commented on Sep 2, 2026

    @os-warren
    CollaboratorAuthor

    Claiming this issue.

    Scope per the re-scoped card: samples/*.csv, a README section, one header-vs-schema guard test, and browser evidence of the platform's standard Import. No import code.


    Generated by Claude Code

  4. os-warren commented on Sep 2, 2026

    @os-warren
    CollaboratorAuthor
    {
      "issue": 19,
      "status": "done",
      "branch": "claude/issue-19-standard-import",
      "pr": "https://github.com/objectstack-ai/duly/pull/110",
      "premise_still_valid": true,
      "summary": "Made \"import the existing duty list\" demoable through the platform's standard Import, with no import code. Shipped samples/catalog-items.csv (21 rows: recurring 18 / standing 2 / one-off 1, all seven frequencies, grace 0-21) and samples/duties.csv (19 rows with owner, business_unit and catalog_item as natural keys), both describing Ardenline Group, the same fictional manufacturer the seed uses. Walked the Console's three-screen Import (Upload -> Mapping -> Preview) on a clean `pnpm dev` database and recorded it with 10 screenshots in docs/import/walkthrough.md; the README gained an \"Import your existing list\" section with three steps, the column rules and the sample links; test/import-samples.test.ts holds every sample header to the target object's own ObjectSchema. FOUR sample files, not two: on a genuinely clean database there is one sys_user (Dev Admin) and zero sys_business_unit rows, so duties.csv imports 0 of 19 with one `Owner: No matching record` per row — samples/business-units.csv (6) and samples/people.csv (12) create the prerequisite through the same standard Import, and that refusal screen is kept in the walkthrough as the evidence for why the ordering leads the README. LOOKUP RESOLUTION SETTLED (the card's open question): the Import UI resolves natural keys exactly as the seed loader does — `owner` against sys_user.name AND against the user's email, `business_unit` against sys_business_unit.name (its `code` does NOT resolve), `catalog_item` against the item's name, `parent_business_unit_id` against a parent's name within the same file. An unresolvable name skips that row, names it, and offers a `Download failed rows` file; nothing is linked to a best guess. Nothing needed filing upstream. Two judgement calls flagged in the PR body: the README heading is English (\"Import your existing list\") per AGENTS.md rule 8 rather than the card's literal 「导入现有清单」, and test/node-builtins.d.ts declares the single readFileSync overload the test needs instead of adding @types/node, following the precedent src/data/index.ts sets.",
      "tests": "All four gates run under the shared verify lock AFTER the final commit, on tree 04cab67 (= branch HEAD, pushed). Quoting each gate's own verdict line: `pnpm validate` -> \"✓ Validation passed (472ms)\" (plus the one hierarchy-security capability-provider warning AGENTS.md documents as this repo's expected state); `pnpm typecheck` -> no output, clean; `pnpm test` -> \"Test Files 30 passed (30)\" / \"Tests 749 passed (749)\" — 6 of those are the new test/import-samples.test.ts; `pnpm build` -> \"✓ Build complete (839ms)\". Lock wrapper: \"os-verify-lock: VERDICT command-exit 0 · held the lock 46s · waited 27s\". BROWSER EVIDENCE (Playwright/chromium against a live `pnpm dev` on a wiped .objectstack/data, port 4319 to avoid the parallel agents): 6 + 12 + 21 + 19 rows created, 0 skipped, screenshots in docs/import/. Counts and lookups re-read through the REST API, not just off the screen — e.g. \"Lifting equipment check — Line C | owner=Yuki Tanabe | bu=Northgate Operations | catalog_item=Lifting equipment check\", and duly_catalog_item came back as forms {recurring:18, one_off:1, standing:2} with the standing rows carrying all five cadence columns null, i.e. the conditional defaults resolved from blank cells. Negative measurements taken deliberately: duties.csv on a people-less database = \"0 imported / 19 skipped\"; a unit written as its code = \"Row 3: Business unit: No matching record for \\\"NGP-QA\\\"\"; duly_duty.last_dispatched_period (readonly) maps as \"(match only)\", the import reports \"1 created\", and the column reads back null. ABLATION of the new guard: renamed position_code -> position_kode in src/objects/catalog-item.object.ts. No rebuild leg needed or done — vitest resolves ../src/objects/index.js to the TypeScript source, not to a package `exports` dist, so there is no stale-artifact path here. The mutation was proven on disk before any reading, by grepping the exact text in both directions (injected '    position_kode: Field.text({' = 1, removed '    position_code: Field.text({' = 0), not by the editor's exit code; the script carried `trap ... EXIT INT TERM` and the restore leg was confirmed by `git status --porcelain` returning 0 entries. Result was red in the predicted direction: \"FAIL test/import-samples.test.ts > 'catalog-items.csv' → 'duly_catalog_item'\", \"AssertionError: expected [ 'position_kode' ] to deeply equal []\", \"Tests 2 failed | 4 passed (6)\". CI on PR #110 was still converging at report time (mergeable_state: unstable) — per the dispatch contract the draft PR is the hand-off point and CI convergence is the PM's read.",
      "open_questions": [
        {
          "question": "Should samples/ ship the two org prerequisite CSVs (business-units.csv, people.csv), or only the two the card names?",
          "options": [
            "A — keep all four (shipped). The documented walk is reproducible on a clean `pnpm dev` database in three steps, entirely through the platform's Import; the org files are labelled as the prerequisite and create directory rows, not logins.",
            "B — ship only catalog-items.csv and duties.csv, and reword step 1 to 'your people and units are already in the platform (SSO / directory sync)'. Two files, but the README's own walk then cannot be followed end to end on a fresh dev database — duties.csv imports 0 of 19."
          ],
          "recommendation": "A, because #19's acceptance criterion is a screenshot proving BOTH named samples land with matching row counts, and on a clean database that is unreachable without the lookup targets existing. The extra files add no new interface — same Import button, same wizard — and the failure they prevent is exactly the one the card asked to be recorded honestly. B is a one-commit revert if you would rather the repo shipped only the two; the PR body says so."
        },
        {
          "question": "The README section is headed \"Import your existing list\"; the card names it 「导入现有清单」.",
          "options": [
            "A — English heading (shipped), consistent with AGENTS.md rule 8 (English is the source language) and with the rest of the README, which is entirely English.",
            "B — the literal Chinese heading, if the card meant the string rather than the topic."
          ],
          "recommendation": "A. The zh-CN bundle is where translated display text lives; a single Chinese heading in an otherwise English README would be the only one in the file. One word to change if B was meant."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. os-warren commented on Sep 2, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    Re-posted: GitHub's body sanitizer ate the <!-- os-dev-report --> HTML-comment marker on the previous comment (verified by reading it back — the body now begins with the fenced block), which makes that copy invisible to a scan for the marker. This one carries the marker as literal text. Same report, no changes; the previous comment is superseded, not a second run.

    {
      "issue": 19,
      "status": "done",
      "branch": "claude/issue-19-standard-import",
      "pr": "https://github.com/objectstack-ai/duly/pull/110",
      "premise_still_valid": true,
      "summary": "Made \"import the existing duty list\" demoable through the platform's standard Import, with no import code. Shipped samples/catalog-items.csv (21 rows: recurring 18 / standing 2 / one-off 1, all seven frequencies, grace 0-21) and samples/duties.csv (19 rows with owner, business_unit and catalog_item as natural keys), both describing Ardenline Group, the same fictional manufacturer the seed uses. Walked the Console's three-screen Import (Upload -> Mapping -> Preview) on a clean `pnpm dev` database and recorded it with 10 screenshots in docs/import/walkthrough.md; the README gained an \"Import your existing list\" section with three steps, the column rules and the sample links; test/import-samples.test.ts holds every sample header to the target object's own ObjectSchema. FOUR sample files, not two: on a genuinely clean database there is one sys_user (Dev Admin) and zero sys_business_unit rows, so duties.csv imports 0 of 19 with one `Owner: No matching record` per row — samples/business-units.csv (6) and samples/people.csv (12) create the prerequisite through the same standard Import, and that refusal screen is kept in the walkthrough as the evidence for why the ordering leads the README. LOOKUP RESOLUTION SETTLED (the card's open question): the Import UI resolves natural keys exactly as the seed loader does — `owner` against sys_user.name AND against the user's email, `business_unit` against sys_business_unit.name (its `code` does NOT resolve), `catalog_item` against the item's name, `parent_business_unit_id` against a parent's name within the same file. An unresolvable name skips that row, names it, and offers a `Download failed rows` file; nothing is linked to a best guess. Nothing needed filing upstream. Two judgement calls flagged in the PR body: the README heading is English (\"Import your existing list\") per AGENTS.md rule 8 rather than the card's literal 「导入现有清单」, and test/node-builtins.d.ts declares the single readFileSync overload the test needs instead of adding @types/node, following the precedent src/data/index.ts sets.",
      "tests": "All four gates run under the shared verify lock AFTER the final commit, on tree 04cab67 (= branch HEAD, pushed). Quoting each gate's own verdict line: `pnpm validate` -> \"✓ Validation passed (472ms)\" (plus the one hierarchy-security capability-provider warning AGENTS.md documents as this repo's expected state); `pnpm typecheck` -> no output, clean; `pnpm test` -> \"Test Files 30 passed (30)\" / \"Tests 749 passed (749)\" — 6 of those are the new test/import-samples.test.ts; `pnpm build` -> \"✓ Build complete (839ms)\". Lock wrapper: \"os-verify-lock: VERDICT command-exit 0 · held the lock 46s · waited 27s\". BROWSER EVIDENCE (Playwright/chromium against a live `pnpm dev` on a wiped .objectstack/data, port 4319 to avoid the parallel agents): 6 + 12 + 21 + 19 rows created, 0 skipped, screenshots in docs/import/. Counts and lookups re-read through the REST API, not just off the screen — e.g. \"Lifting equipment check — Line C | owner=Yuki Tanabe | bu=Northgate Operations | catalog_item=Lifting equipment check\", and duly_catalog_item came back as forms {recurring:18, one_off:1, standing:2} with the standing rows carrying all five cadence columns null, i.e. the conditional defaults resolved from blank cells. Negative measurements taken deliberately: duties.csv on a people-less database = \"0 imported / 19 skipped\"; a unit written as its code = \"Row 3: Business unit: No matching record for NGP-QA\"; duly_duty.last_dispatched_period (readonly) maps as \"(match only)\", the import reports \"1 created\", and the column reads back null. ABLATION of the new guard: renamed position_code -> position_kode in src/objects/catalog-item.object.ts. No rebuild leg needed or done — vitest resolves ../src/objects/index.js to the TypeScript source, not to a package `exports` dist, so there is no stale-artifact path here. The mutation was proven on disk before any reading, by grepping the exact text in both directions (injected 'position_kode: Field.text({' = 1, removed 'position_code: Field.text({' = 0), not by the editor's exit code; the script carried `trap ... EXIT INT TERM` and the restore leg was confirmed by `git status --porcelain` returning 0 entries. Result was red in the predicted direction: \"FAIL test/import-samples.test.ts > 'catalog-items.csv' -> 'duly_catalog_item'\", \"AssertionError: expected [ 'position_kode' ] to deeply equal []\", \"Tests 2 failed | 4 passed (6)\". CI on PR #110 was still converging at report time (mergeable_state: unstable) — per the dispatch contract the draft PR is the hand-off point and CI convergence is the PM's read.",
      "open_questions": [
        {
          "question": "Should samples/ ship the two org prerequisite CSVs (business-units.csv, people.csv), or only the two the card names?",
          "options": [
            "A — keep all four (shipped). The documented walk is reproducible on a clean `pnpm dev` database in three steps, entirely through the platform's Import; the org files are labelled as the prerequisite and create directory rows, not logins.",
            "B — ship only catalog-items.csv and duties.csv, and reword step 1 to 'your people and units are already in the platform (SSO / directory sync)'. Two files, but the README's own walk then cannot be followed end to end on a fresh dev database — duties.csv imports 0 of 19."
          ],
          "recommendation": "A, because #19's acceptance criterion is a screenshot proving BOTH named samples land with matching row counts, and on a clean database that is unreachable without the lookup targets existing. The extra files add no new interface — same Import button, same wizard — and the failure they prevent is exactly the one the card asked to be recorded honestly. B is a one-commit revert if you would rather the repo shipped only the two; the PR body says so."
        },
        {
          "question": "The README section is headed \"Import your existing list\"; the card names it 「导入现有清单」.",
          "options": [
            "A — English heading (shipped), consistent with AGENTS.md rule 8 (English is the source language) and with the rest of the README, which is entirely English.",
            "B — the literal Chinese heading, if the card meant the string rather than the topic."
          ],
          "recommendation": "A. The zh-CN bundle is where translated display text lives; a single Chinese heading in an otherwise English README would be the only one in the file. One word to change if B was meant."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. added a commit that references this issue on Sep 2, 2026
    56b6aae
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions