Skip to content

Module split: each module package registers its own translations (ledger move rows R6, R7, R14, M3, M4, S3, S4) #2352

Open

Description

@objectstack-fleet

Source

The module-split hard-edge ledger, docs/architecture/module-split-hard-edges.md (#2311, landed as 3f5622d), measured each module absent on 18.2.0. Seven of its rows are marked move: fixed inside this repository by registering an item with the package that owns its target, with no platform change needed. All seven are translations that the app package's locale packs (src/sales/translations/) carry for module items. With the module absent, each one is a G2 translation-target-unknown refusal that stops the compile.

Row Today, in the app package Target G2 refusals
R6 src/sales/translations/{en,zh-CN,es-ES,ja-JP}.ts → */objects.commerce.ts, */objects.pipeline.ts the six revenue objects 24
R7 */app.ts navigation nav_quote, nav_product, nav_price_book revenue's contributed nav ids 9
R14 flows.quote_generation (zh-CN app.ts) the revenue flow part of 7
M3 *.ts → */objects.marketing.ts crm_campaign, crm_campaign_member 8
M4 */app.ts navigation.nav_campaign, flows.campaign_enrollment (and the two enroll flows' refusals since #2336) marketing nav id and flows 6+
S3 *.ts → */objects.service.ts the three service objects 12
S4 */app.ts service nav ids, dashboards.service_dashboard, the three case reports, flows.claim_case/close_case/escalate_case service nav, dashboard, reports, flows 36

Target

Each module package (src/revenue/, src/marketing/, src/service/) registers the translations of the items it owns, in all four locales, from its own package. The app's packs keep only the app's own items.

  • Strings move, they are not re-translated: each one keeps its exact wording.
  • ⛔ R14 is half a move. objects.crm_opportunity._actions.generate_quote stays in the app pack, because the action is on an app object and leaves only with platform finding F3 (row R11). Only flows.quote_generation moves.
  • Translations for an item that is still app-registered also stay, until that item moves.

Mechanism assumption (measure first, on the installed 18.2.0)

A module package can register a translation bundle for its own objects and contributed nav ids, and the console resolves it exactly as it does the app's. os explain translation-target-unknown says a contributing package translates its own contributed ids. ⛔ If the platform refuses this, stop and report it. That would be a platform card, not a workaround.

Acceptance

  1. Every string the console showed before still shows in en, zh-CN, es-ES and ja-JP, checked by the existing i18n suites and a browser spot check in zh-CN on a revenue, a marketing and a service screen.
  2. A test fails when an app pack carries a key for an object, nav id, dashboard, report or flow that a module package owns.
  3. The ledger rows R6, R7, R14 (flow half), M3, M4, S3 and S4 are marked resolved, and a scratch-tree re-run of G2 with each module absent shows those refusals gone.

File surface

src/sales/translations/**, new or existing translations/ folders under src/revenue/, src/marketing/ and src/service/, each module's index.ts registration, the ledger doc, tests, and one changeset.

Blocked-by: objectstack-ai/objectstack#27086
Blocked-by: objectstack-ai/objectstack#27087

Relation: part of the #2271 carve-out preparation. It does not unblock #2271 alone; F1–F6 gate it.

Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from the ledger's move rows, as #2311's landing said the seat would.

Activity

  1. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch order · #2352 · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-28T00:07Z

    This order takes the slot freed by #2344 (PR #2351, ACCEPTed with auto-merge armed). Irene asked for dispatch to continue (「继续派发」) at lane concurrency 5. The card body is the spec, and its rulings, including R14's half move, are not re-decided.

    Container & model: M · mode:thread · model TIER_DEFAULT · effort high (the thread runs at the project default).
    Role file: objectstack-ai/objectstack .claude/agents/os-dev.md. Read it in full. hotcrm AGENTS.md binds. Re-measure every file:line against origin/main (b8332aa or later). The ledger was measured at ea567f2. Since then, #2336/#2343 added enroll-flow refusal translations, #2338 added opportunity bulk-delete labels and the Lead Engagement nav label, and #2326/#2242 changed nothing in the packs. Re-count every row before you move it.
    Writes:

    • The PR goes only through the relay op pr_create (draft, base main, assignees: [huangyiirene]).
    • Comments go only through scripts/pm/post-stamped.mjs.
    • ⛔ No GitHub MCP write. ⛔ No issue filing: out-of-scope findings and a platform refusal go in the report.
    • The write budget is tight, so make two writes: the PR and the report.

    Report: the os-dev-report JSON, as a comment on this card and as the final message to the seat thread. ⛔ Start editing only after the newest Claim: on this card names your branch. Push work in progress at each milestone, so a stop loses nothing.

    PM mechanism assumptions (measure first, on the installed 18.2.0)

    1. A module package registers its own translation bundle, and the console resolves its object labels, contributed nav ids, dashboards, reports and flow labels exactly as before. Prove it in zh-CN on one screen per module before moving the rest. A refusal is a platform card: stop and report it with evidence.
    2. Moving a key leaves the app pack's translation-target-unknown count with each module absent at the ledger's remainder. Re-run the ledger's scratch-tree G2 for each module and report the numbers.

    Suggested route (optional)

    • Move one module end to end first (service has the most rows), measure it, then do the other two.
    • Add a test that fails when an app pack names a module-owned key.
    • Mark the ledger rows resolved.
    • Add a changeset (patch; no user-visible change).

    Serial constraints: #2327 (in-app docs, branch 4tg14s) adds Help nav labels to the four app.ts packs. Whichever lands second merges origin/main. #2338 items 14–15 and #2349 do not touch the packs.


    Generated by Claude Code

  2. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 28, 2026
  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01J8VVAnPkgK6CWrJa6t7Fk5
    Account: huangyiirene
    Branch: claude/project-thread-53k1qh
    Worktree: hotcrm-issue-2352 (card thread; branch is platform-designated, the measured platform exception to claude/issue-N-slug; implementing session session_01J8VVAjG6xU6c1mQQ6rN6Wd)
    Domain: repo:hotcrm (single-lane repo, no domain:*)
    Seat: repo:hotcrm#1
    File surface: src/sales/translations/**, new or existing translation folders under src/revenue/, src/marketing/, src/service/, each module's index.ts registration, docs/architecture/module-split-hard-edges.md, tests, one changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (sister repo, hand-derived), effort: the order asked high; the thread runs at the project default, which only the maintainer sets
    Clause-②: no
    Metadata-protocol: none
    Responsibility: each module package registers its own translations (ledger move rows R6, R7, R14 flow half, M3, M4, S3, S4); strings move unchanged
    Thread-read: 6456901763
    Premise: holds — re-read at claim on hotcrm origin/main 1f1ea12 (order comment 6456901763)
    Serial constraints cleared: #2327 (branch 4tg14s) adds Help nav labels to the four app.ts packs; whichever lands second merges origin/main

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 2352,
      "status": "blocked",
      "branch": "claude/project-thread-53k1qh",
      "pr": null,
      "session": "session_01J8VVAjG6xU6c1mQQ6rN6Wd",
      "premise_still_valid": false,
      "summary": "Mechanism assumption 1 holds at run time and fails at author time. Run time: with the service move in place, GET /api/v1/i18n/translations/zh-CN served the module's keys deep-merged with the app's (crm_case 工单, nav_case and nav_home both present, service_dashboard widgets, claim_case, sla_performance, app label HotCRM), and the zh-CN console showed 服务概览 / 工单 / 知识库 / 我的工单 in the sidebar and the 客户服务 dashboard with translated widget titles (screenshots: project files issue-2352/01-zh-service-dashboard.png, 02-zh-case-list.png). Author time, two platform refusals: (F-a) TranslationData requires apps.APP.label, so a module carrying only its contributed nav ids fails defineStack and tsc; (F-b) the os lint coverage merge folds a second bundle shallowly, so every moved non-object row reads as i18n/missing-* under --strict. Per the order I stopped and opened no PR. The complete move for all three modules is pushed as a red WIP commit f9efb5e (42 files, strings cut verbatim by a block-cut script, nothing re-translated). Not done because of the stop: the packFor fix in test/helpers/composed-stack.ts, the ownership guard test, the ledger rows, the changeset, and the browser check on a revenue and a marketing screen.",
      "tests": "pnpm test not run on the WIP (validate is red, so the suite would measure a refused tree); the main baseline run hit the 580 s foreground cap (exit 124), so a full run belongs in the background. Shape measurements at 08bbdd5 + the move: FULL MOVE (f9efb5e): tsc exit 1, 9x TS2741 'label is missing' on apps.crm_enterprise in each module's zh-CN, es-ES and ja-JP pack; os validate exit 1, 'translations.0.zh-CN.apps.crm_enterprise.label: Required property label is missing' x3. FULL MOVE + app label restated in each module pack (measurement only, never committed): validate exit 0; lint --strict exit 1, 330 warnings (missing-navigation 105, missing-widget 84, missing-dataset 48, missing-flow 39, missing-page 30, missing-report 18, missing-dashboard 6), each one a key the runtime serves. OBJECTS ONLY (module packs carry objects, app.ts untouched): typecheck exit 0, validate exit 0, lint exit 0. G2 with each module absent (validate --json, P1 grant peel plus a peel of the app actions that launch an absent module's flow, so G1/F3 does not mask G2), translation-target-unknown count: service absent 55 at base, 43 objects-only, 0 full move; revenue absent 40 / 16 / 4; marketing absent 28 / 20 / 8. The full-move remainders are exactly the non-move rows: objects.crm_opportunity x4 (R14 action half) for revenue, objects.crm_contact x4 and objects.crm_lead x4 (M9) for marketing. Re-counted move rows at 08bbdd5: S3 12, S4 43 (ledger said 36: 21 nav, 4 dashboard, 9 flows, 9 reports), R6 24, R7 9, R14 flow half 3, M3 8, M4 12 (ledger 6+: 3 nav, 9 flows incl. the two enroll flows since #2336). Beyond the G2-judged rows, the move also carries datasets.case_metrics, datasets.product_metrics and pages.case_detail_page too (not G2-judged, owned by the modules).",
      "gates": "Local, captured before any pipe: tsc and os validate exit 1 on f9efb5e (F-a); lint not reachable there. hygiene, hygiene:tokens, build, test: NOT MEASURED, reason: stopped on the platform refusal before a green tree existed. No PR, so no CI check-runs.",
      "dogfood_battery": "n/a — no PR, hotcrm has no dogfood battery",
      "mcp_calls": "3 — mcp__github__issue_read (get x1, get_comments x2); no write tool",
      "api_writes": "1 — this os-dev-report comment on hotcrm #2352 via scripts/pm/post-stamped.mjs; git push of the WIP branch is not a REST write",
      "open_questions": [
        {
          "question": "Both refusals are platform behaviour on 18.2.0 and on objectstack main f1cd529d. How should #2352 proceed?",
          "options": [
            "A — Hold #2352 blocked on two upstream cards (F-a, F-b) and resume from f9efb5e once a pinned release carries both: re-run tsc, validate, lint --strict, the G2 table and the browser check, then add the packFor fix, the guard test, the ledger rows and the changeset. Cost: none of the 111 move-row refusals leave until the pin moves.",
            "B — Land the objects rows now (R6, M3, S3: 44 refusals, measured green on typecheck, validate and lint for all three modules; patch in project files issue-2352/objects-only-shape.patch) as Part of #2352, and leave R7, R14 flow half, M4 and S4 (67 refusals) blocked on F-a and F-b. Cost: AGENTS.md platform rule 2 forbids landing the half we can land now, so this needs the maintainer's ruling that the objects rows are separate ledger rows the defects do not touch.",
            "C — Restate the app label in every module pack to get past F-a. Rejected: it copies the app's string into three packages and lint still fails 330 times on F-b."
          ],
          "recommendation": "A, because AGENTS.md rule 2 binds and no ruling yet separates the objects rows; B is the faster route if Irene rules on it."
        }
      ],
      "out_of_scope_findings": [
        "class: b · reach: os validate exit 1 and tsc TS2741 on hotcrm f9efb5e · F-a: TranslationData apps.APP.label is required (objectstack packages/spec/src/system/translation.zod.ts:944 on main f1cd529d; 18.2.0 spec dist chunk-OW4VIKZG.mjs:520), while os explain translation-target-unknown says a stack that only contributes into an app translates the contributed ids alone. A module cannot carry apps.crm_enterprise.navigation.nav_case without restating the owning app's label. Seam: spec:TranslationData.apps.APP.label → runtime:defineStack translations validation · dedupe words: translation apps label required, contributed navigation ids translation, navigationContributions translation bundle, module package nav label",
        "class: a · reach: os lint --strict exit 1, 330 false i18n/missing-* on hotcrm f9efb5e plus app label restated, while GET /api/v1/i18n/translations/zh-CN serves every key · F-b: packages/cli/src/utils/i18n-coverage.ts mergeData (line 176 on main f1cd529d) folds a second bundle per locale for objects (per object), globalActions, apps (a later apps.APP replaces the whole earlier entry, navigation included), messages and metadataForms only; dashboards, datasets, pages, flows, reports, picklists and the rest of a later bundle are dropped. The runtime (service-i18n deepMerge) merges all of them, so a multi-package artifact whose packages each register translations cannot pass lint --strict. Seam: spec:stack.translations (concat) → cli:computeI18nCoverage flattenBundles mergeData · dedupe words: i18n coverage mergeData shallow, flattenBundles multiple bundles, missing-navigation false positive, translation bundle per package lint",
        "class: none (observation) · AGENTS.md says the revenue module owns contract, but crm_contract is registered by the app package (src/sales/objects/contract.object.ts), so its translations correctly stay in the app pack · carrier: none"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked on the platform · pm:dispatched → pm:blocked · repo:hotcrm seat 1 · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-28T04:49Z

    Blocked-by: objectstack-ai/objectstack#27086
    Blocked-by: objectstack-ai/objectstack#27087

    The card thread's report (comment 6460879964) measured mechanism assumption 1 as holding at run time and failing at author time. In the zh-CN console, a module-registered bundle is served deep-merged with the app's, and the service screens render translated. Two platform refusals stop the compile:

    • F-a → objectstack#27086: TranslationData requires apps.<app>.label. A module pack that carries only its contributed nav ids fails defineStack, tsc and os validate.
    • F-b → objectstack#27087: the lint coverage merge folds a second bundle shallowly. Every moved non-object row reads as i18n/missing-* under --strict (330 warnings).

    The seat's ruling is A. The card holds blocked, and the claim on claude/project-thread-53k1qh stands. The full move for all three modules is pushed as WIP f9efb5e. Once an npm-published @objectstack/* carries both fixes and hotcrm pins it, the thread resumes from that commit. It re-runs tsc, validate, lint --strict, the G2 table and the browser check, then adds the packFor fix, the ownership guard test, the ledger rows and the changeset.

    Not chosen:

    • B would land the objects rows (R6, M3, S3) now. It needs a maintainer ruling past AGENTS.md platform rule 2, and the seat does not take that ruling itself.
    • C would restate the app label in each module pack. It is rejected: it copies a string across three packages and lint still fails on F-b.

    Re-counted at 08bbdd5: S4 is 43, not 36. M4 is 12, not 6+. The ledger is corrected when this resumes.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions