Skip to content

Seed.locale: marketplace install, reseed sample data and per-org replay should select datasets by the installing organization's locale #30281

Closed

Description

@objectstack-fleet

Source

Asked by HotCRM's maintainer, who wants Chinese customers to get Chinese demo data when they install HotCRM. Ruled option A on 2026-11-05: the platform's install paths pick datasets by locale, and HotCRM ships zh-CN datasets. The maintainer set every card of this work to priority p1.

Problem (measured on 18.3.0 and objectstack main 797f925)

Seed.locale (#16595) lets an app ship one demo dataset per language market. The only place it is read is AppPlugin.resolveSeedLocale() (packages/runtime/src/app-plugin.ts). That method reads the bundle's static i18n.defaultLocale, and three call sites use it: the boot seed, the per-org seed-replayer (captured once at boot), and dev hot-reload.

These paths pass no locale:

  • marketplace install, reseed-sample-data and the rehydrate healer (runInlineSeed in packages/cloud-connection/src/marketplace-install-local-plugin.ts);
  • package apply and draft publish.

When a locale-scoped dataset reaches one of them, every dataset loads and the loader logs a warning. The spec records this as a decision (packages/spec/src/data/seed.zod.ts, locale doc: "Publishing and installing are locale-neutral acts", ruled on #17011, 2026-09-10).

As a result, an app cannot give a Chinese tenant Chinese sample data from one package. Two things happen instead:

  • if the app adds locale: ['zh-CN'] datasets, every marketplace tenant gets both the English and the Chinese rows;
  • one server's per-org replay gives every organization the same boot locale.

The tenant setting localization.locale (service-settings, scope: 'tenant') already exists. The seed path never reads it.

Ask (maintainer ruling 2026-11-05, Irene, option A; reverses #17011's install-path bound)

  1. Marketplace install, reseed sample data and the rehydrate healer pass a locale to the seed load request: the installing organization's effective localization.locale (cascade platform → global → tenant), or else the installing user's sys_user.locale, or else the app's i18n.defaultLocale.
  2. The per-org seed-replayer resolves the locale per organization, not once at boot.
  3. Decide what happens at new-org founding, when the replay fires on sys_organization insert, possibly before the org has a localization.locale. Options:
    • (a) use the founding user's sys_user.locale;
    • (b) defer the replay until the locale is set;
    • (c) fall back to i18n.defaultLocale and let the tenant reseed later.
  4. Keep the existing behaviour for datasets without locale: they stay unrestricted.

Consumer

hotcrm: zh-CN product catalogue and standard price book (SaasTenantSeedData), as locale: ['zh-CN'] datasets beside the English ones. The hotcrm change merges only after a pinned release carries this card. Until then, marketplace tenants would seed both languages.


Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p1 kept · domain:server kept · pm:queue · 2026-11-05T04:54Z

    ⛔ Not a claim.

    • Read fresh. Ruled A (2026-11-05): the marketplace install, the sample-data reseed and the per-org replay pick a seed dataset by the installing organization's locale. Today only AppPlugin.resolveSeedLocale() reads Seed.locale, so every locale's dataset loads. HotCRM is waiting to ship zh-CN demo data, so p1 kept.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Correction from the consumer · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-11-05T05:04Z

    The Problem section says that a marketplace tenant gets both the English and the Chinese rows. For HotCRM that is not what happens. Measured on 18.3.0 by the hotcrm#2411 thread (branch claude/project-thread-ypnm82, commit 69b4694), with a seed load that carries no locale, the way the install paths load today:

    • The English rows land first.
    • Every zh-CN twin is then refused. The products hit the per-org SKU unique key, and the price book hits the one-Standard-book rule.

    So a Chinese tenant ends up with the English catalogue only, plus a page of refused-insert warnings. The ask stands as written. Only the observed symptom differs: it is not duplicated data. It is the wrong language plus warning noise. Datasets without shared unique keys would still load twice.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

    Claim: PM loop round 1
    Session: session_01Utp6CZMkHvuDeVYgTD73qF (seat) · dev card thread session_01Utp6CafJkuNKWHNo7WLtiP
    Account: os-bill
    Branch: claude/project-thread-cydyoy (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-30281
    Domain: domain:server
    Seat: domain:server#4
    File surface: packages/runtime/src/app-plugin.ts (resolveSeedLocale and its callers) and the per-org seed-replayer it wires, packages/cloud-connection/src/marketplace-install-local-plugin.ts (runInlineSeed and its three call sites only), the locale read from service-settings, their tests, one changeset; amended on the dev's measurement: runInlineSeed's call sites are the rehydrate healer (:1391), cloud-connection's own fallback per-org replayer (:1648) and applySideEffects step 3 (:5011, reached by install and reseed), and packages/plugins/organizations/src/organizations-plugin.ts gets one line so the founding replay receives the creating user (item 3 lands on (a) with (c) as the fallback, by construction). ⛔ packages/spec/** (the Seed.locale docblock is a spec-lane follow-up), docs/adr/**, .claude/**, AGENTS.md, ui/**, skills/** (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default judgment tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: xhigh
    Clause-②: yes (widening) — @objectstack/runtime exports the shared seed-locale resolver (precedent: mergeSeedDatasets, which cloud-connection imports the same way); nothing is removed or narrowed (amended from no on the dev's measurement)
    Metadata-protocol: semantic
    Responsibility: n/a — not a defect card
    Thread-read: 6650196621
    Premise: holds
    Serial constraints cleared: seats 2, 3 and 5 confirmed (2026-11-05T05:21Z). Seat 3's #30217 edits the same marketplace-install-local-plugin.ts in its constructor and rehydrate section only; whoever merges second merges origin/main first.

    Dispatch order: /mnt/project-files/server-seat4/dispatch-30281.md (project shared folder). Ruling section: maintainer ruling option A (2026-11-05, quoted in the card body) and triage audit 6650042287. Item 3 (new-org founding: a / b / c) is measured first; if item 1's cascade does not settle it, the dev posts needs_decision and carries on with items 1, 2 and 4. If an exported option is added, the dev declares yes (widening) and the seat amends this line.

    Posted 2026-11-05T05:24Z.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
    os-dev-report

    Status: done. Draft PR #30346 carries Fixes #30281, head 6d3a814a4.

    What it does, under ruling A (2026-11-05):

    • One producer, resolveSeedLocaleForLoad (@objectstack/runtime, new seed-locale.ts): the organization's localization.locale when someone set it (a source: 'default' answer does not count, as applyLocalizationLocale and organization-default-currency.ts already read it), else the person's sys_user.locale, else the app's i18n.defaultLocale. A package with no locale-scoped dataset reads neither.
    • Install, sample-data reseed and restart heal (runInlineSeed and its three call sites in marketplace-install-local-plugin.ts) ask it with the active organization, the admitted user and the package's default; the heal asks with no organization and no user.
    • Per-organization replay resolves per organization at replay time (AppPlugin and the cloud-connection replayer). The seed-replayer callable takes (organizationId, { userId }); Middleware B passes the insert's attributed user as the founder.
    • Item 3 lands on (a), the founder's language, with (c), the app's default, behind it, by construction: at insert time the organization has no setting yet. Both paths are pinned. No needs_decision, as the seat agreed.
    • Unscoped datasets stay unrestricted (pin 4).

    Deviations, all agreed with the seat before or during the Claim: the call-site mapping follows the measured code, not the dispatch's line numbers. organizations-plugin.ts joined the file surface, so Clause-② is yes (widening). Six sibling cloud-connection suites gained three producer stubs in their runtime mock. The hand-written docs page content/docs/data-modeling/seed-data.mdx is corrected in the PR. The spec docblock is follow-up owed.

    {
      "issue": 30281,
      "status": "done",
      "branch": "claude/project-thread-cydyoy",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30346",
      "session": "session_01Utp6CafJkuNKWHNo7WLtiP",
      "premise_still_valid": true,
      "summary": "Seed.locale now selects datasets on the marketplace install, the sample-data reseed, the restart heal and the per-organization replay. A new runtime producer resolveSeedLocaleForLoad answers the organization's localization.locale when set (source not 'default'), else the person's sys_user.locale, else the app's i18n.defaultLocale; cloud-connection runInlineSeed and AppPlugin's replayer call it per load, and organizations Middleware B passes the founder. Item 3 settles on (a) with (c) as fallback by construction; datasets without locale stay unrestricted; boot seed, hot-reload, package apply and draft publish are unchanged.",
      "tests": "At head 6d3a814a4 under the verify lock: runtime app-plugin.seed-locale-producer.test.ts 22 passed (15 new), cloud-connection marketplace-install-local-seed-replayer.test.ts 16 passed (7 new), organizations organizations-plugin.test.ts 20 passed (2 new). Full suites on c6795fe0a (same package files as head): runtime 541 files 8635 passed 19 skipped; cloud-connection 70 files 853 passed; organizations 18 files 250 passed; typecheck of the three exit 0. Eleven ablations through scripts/ablation-replace.mjs, one per resolution point, each red then restored (settings rung, default-not-a-choice, user rung, app-default rung, no-scoped-no-read, AppPlugin per-org, Middleware B founder, install/reseed user, heal body, cc replayer founder, locale reaching the loader).",
      "gates": "dispatch-gates --commands derived 95 families; each run at head 6d3a814a4 after a full build (119/119) with its exit recorded before any pipe: 95 of 95 exit 0. --ran: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. pnpm lint exit 0. pnpm install --frozen-lockfile exit 0. CI check-runs on #30346: not yet reported.",
      "dogfood_battery": "n/a — no ! in the PR title and Clause-② has no (narrowing) arm",
      "line_budget": "source +264 -27 (5 files), tests +498 (9 files), docs and changeset +57 -9 (2 files); 16 files",
      "mcp_calls": "3: mcp__github__issue_read x3 (get, get_comments x2); no GitHub write tool. Besides GitHub MCP: one subscribe_pr_activity on #30346 (session event subscription, writes nothing to GitHub)",
      "api_writes": "2 through the relay: pr_create #30346 (draft, no assignee, no labels, read-back identical) and this comment. Plus 5 git pushes to claude/project-thread-cydyoy (not REST)",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: seat · follow-up owed — packages/spec/src/data/seed.zod.ts locale docblock and .describe() still say the install paths do not filter by locale (install paths now pick by the organization's locale, ruling A 2026-11-05); the describe regenerates content/docs/references/data/seed.mdx · Tier H path, not edited",
        "carrier: none · noted, not filed — a sys_user.locale stored as a bare tag (zh) does not select a zh-CN dataset because the loader matches tags exactly; the Localization setting offers only region-qualified tags · in the PR's Acceptance notes"
      ]
    }

    Report taken 2026-11-05T06:53Z.


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

    Landed · PR #30346

    Reading Value
    PR #30346 MERGED 2026-11-05T07:45Z, merge commit 8589756ac; Fixes #30281 closed this card. Seat ACCEPT 6652170771 on the landed head 6d3a814a.
    main source packages/runtime/src/seed-locale.ts:123 export async function resolveSeedLocaleForLoad(

    Follow-up filed: #30347 (spec lane), the Seed.locale docblock and .describe() in packages/spec/src/data/seed.zod.ts, which still say installing is locale-neutral; its serial constraint on this PR is now met.

    State change: pm:dispatched removed, assignee cleared. Ruling option A is carried out on all four paths; nothing is left on this card.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions