Repository navigation
Package src/revenue and src/marketing as their own modules inside the one HotCRM artifact, the way src/service is: navigation by contribution, permission sets and cross-domain dashboards stay in the app (objectstack#25403 B′, step one) #2270
Description
Ruled: 6382429507 · batch #8 item 1 re-ruled · option 1 · 2026-10-24T08:34:47Z (supersedes 6377676423 letter A: package revenue and marketing now; quote_generation, the two campaign member-enroll flows and product_metrics stay registered by the app package — files unmoved, no sales → module import — until a released @objectstack/* carries objectstack-ai/objectstack#25514, then they move to their modules)
维护者速读
- 事由: 你在 objectstack#25403 裁了 B′(声明依赖 + 安装前检查),并同意按建议的顺序把 HotCRM 拆成模块。现在只有 service 是独立的包;revenue(产品、报价、合同、明细行)和 marketing(市场活动、活动成员)的目录虽已分开,但仍由 sales 应用包代为注册。
- 要做: 照 service 的做法,把 revenue 和 marketing 各自做成
type: module的包。它们仍在同一个发布物里,依赖app.objectstack.hotcrm。导航改为由模块贡献进应用原有的分组;权限集与跨域仪表盘仍留在应用包。这一步不等平台。 - 完成标准: 发布物里有四个包,对象归属正确,由测试钉住。侧边栏的分组不变;唯一可见的变化是 Sales 组里 Sales Performance 排到了 Quotes / Contracts / Products 前面(模块贡献的条目排在应用自己的条目之后,与 Packaging: sales becomes the
type: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]#1907 的 My Cases 同一条规则)。种子数据、SaaS 组合、CRM Overview 的 Top Products 图表与改动前实测一致。
Unblocked 2026-10-24 by the re-ruling above (pm:blocked → pm:dispatched). Follow-up still owed on objectstack-ai/objectstack#25514: once a released @objectstack/* carries it, register the four items in their modules and empty appRegisteredModuleItems and the test/package-ownership.test.ts roster.
Filing gate ③ a task the maintainer directed: director batch #7 item 1, ruled B′, recorded on objectstack-ai/objectstack#25403 (comment 6369704387). The maintainer's words, verbatim, in the director thread on 2026-10-23: 「如果这样,hotcrm 岂不是真实的可以分拆出很多模块」 (16:25:59Z), 「而每个包可以独立安装」 (16:26:18Z), 「同意」 (16:28:30Z, on the director's recommendation B′ and the suggested order). The suggested order, as that comment records it, names a HotCRM module shipping on its own as the platform's first consumer (revenue first, ADR-0130 §1.3(c)) and keeps composite products shipping as one artifact until the pre-install check lands. This card is the in-artifact step of that order. Epic #1904 left this step "not filed until the maintainer says so"; the words above are that instruction. Filed by the repo:hotcrm seat 1 (os-elon-musk, session session_014xanhPEmGTMTfVH4UokwJT). ⛔ Not a claim.
needs-user-decision and is not dispatched.
Grade: priority:p3, the seat's first-touch grade. No step of the road breaks and no checklist item fails: the artifact installs and works today. The card's value is the exhibit: it shows ADR-0130's one-artifact, N-package boundary over the whole product. It is also the shape the B′ ADR names as its first consumer. The maintainer can overrule the grade here in one word.
Reader (who acts): the repo:hotcrm lane's queue. A seat claims it in label order, as an M card (mode:thread on a project-thread seat).
Serial: after #2268 (in flight, pm:dispatched, no PR yet). Both cards edit src/sales/apps/crm.app.ts and content/docs/getting-started/quick-tour*.mdx. Nothing else open touches objectstack.composition.ts, objectstack.config.ts or src/{revenue,marketing}/ as of filing.
Governing text
docs/architecture/module-split-plan.md, Decisions recorded (2026-09-14):- item 5:
app.objectstack.hotcrm.revenueownscrm_product,crm_opportunity_line_item,crm_quote,crm_quote_line_itemandcrm_contract.app.objectstack.hotcrm.marketingownscrm_campaignandcrm_campaign_member. All aretype: moduleandnamespace: 'crm', and each depends on the app. The graph is a star. - item 6: a directory is a package.
- item 7: imports follow the edges only.
- item 9: "Revenue and marketing are packaged the same way … packaging either is a manifest, an
index.tsand a navigation contribution, with no move." - Spec of record. Read it in full before the first edit; this card is the pointer, not a second copy.
- item 5:
- Same plan, Decisions recorded (2026-09-02):
- item 1: the two campaign-metrics hooks stay in sales, beside
crm_leadandcrm_opportunity. - item 3: permission sets stay whole in the app package.
- item 1: the two campaign-metrics hooks stay in sales, beside
- objectstack#25403 comment
6369704387, B′ item 4: "Permissions unchanged". Also: "until the check lands, composite products keep shipping as one artifact under ADR-0130." - Packaging: sales becomes the
type: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]#1907 (service packaging, landed by PR feat(packaging): one artifact, two packages — sales is the app package, service a module #2011): the precedent for every mechanism below. Its decision 2: "nav_my_caseslands last inside My Work because contributions append after the app's own children … accepted, to be stated in the landing PR's release note." - AGENTS.md: a platform defect means you WAIT for the platform fix. ⛔ No workaround in this repo.
Mechanism (read on HotCRM origin/main b25a32e)
- What is packaged today.
objectstack.config.ts:48iscomposeStacks([serviceStack, appStack], { manifest: 'preserve' }).objectstack.composition.tshands the app stack every revenue and marketing collection:- objects (:260);
- picklists (:272, revenue's
payment_terms); - actions (:275, marketing's);
- datasets (:281, revenue's
product_metrics); - views (:287);
- email templates (:315, revenue's);
- hooks (
appHooks:194:contractHook,opportunityLineItemHook,productHook,quoteHook,quoteLineItemHook,campaignHook,campaignMemberHook); - flows (
appFlows:218: five revenue, four marketing); - sharing rules (
appSharingRules:337:CampaignLeadershipSharingRules); - seed families (
AppSeedData:487).
src/sales/index.ts:33–41says why: a sales file may not import a module directory, so the root does the assembly "until each is packaged in its own card".
- The precedent to copy.
src/service/index.ts:144createHotCrmServiceStack. A manifest withid,namespace: 'crm', the artifact'sversion,type: 'module',engines,dependencies: { 'app.objectstack.hotcrm': '^6.0.0' }andnavigationContributions. A per-packagerequires, measured by deletion. Its own barrels. Only the seed rows handed in. The app stack getsartifactObjects(objectstack.composition.ts:607) so its whole permission sets may grant on module objects (objectstack#18202). - Navigation that must move (rule R3). An app's own
navigationmay not name another package's object.- In
group_sales(src/sales/apps/crm.app.ts:229):nav_quote:249,nav_contract:253 andnav_product:258. They sit ahead of the app-ownednav_sales_dashboard:259. - In
group_marketing(:381):nav_campaign:386, its only child.
- In
- Edges from the app into the two modules. None is refused inside one artifact as far as anything has measured. Three classes have never been measured, because service had nothing to measure (Packaging: sales becomes the
type: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]#1907 status note):- The CRM Overview dashboard's
top_productswidget bindsproduct_metrics(src/sales/dashboards/crm.dashboard.ts:144). This is the plan's Upstream gaps item 4. - Sales record pages list module objects.
opportunity_detail.page.tslistscrm_quote(:250, :491) andcrm_opportunity_line_item(:265, :496) and picks acrm_product(:335).lead_detail.page.tslistscrm_campaign_member(:548). - An app-object lookup into a module:
crm_opportunity.crm_campaign(src/sales/objects/opportunity.object.ts:360). This is R1, ACCEPTED in the plan's matrix, but in the app → module direction. - Sales hooks, flows and actions also name module objects at run time through
ctx.api:account.hook.ts,contact.hook.ts,opportunity.hook.ts,billing-handoff-closed-won.flow.ts,lead.actions.tsandcontact.actions.ts. That is not an authoring-time reference (plan, R4 section), and they stay where they are (host-object rule).
- The CRM Overview dashboard's
- Seeds.
CrmSeedData(:414) interleaves the packages: products between opportunities and tasks, and contracts / quotes after campaign members. Sales' opportunity seeds name campaigns by natural key (src/sales/data/sales.seed.ts:1032ff.) although campaigns replay later. The SaaS shape registers onlyproducts(SaasTenantSeedData:475), which is a revenue family. - Pins that read the two-package shape today:
test/package-ownership.test.ts(:29–30 ids, :46 "exactly the app package and the service module", :105 the permission-set block);test/saas-composition.test.ts;test/helpers/composed-stack.ts;test/docs-sales-index-navigation.test.ts:166–178, "the Sales group holds nine entries, in this order", read off the app's own children with Sales Performance last;test/docs-sales-index-navigation.test.ts:201, Marketing's children are['nav_campaign'];test/docs-quick-tour-navigation.test.ts:377;test/marketplace-bundle.test.tswithtest/fixtures/composed-artifact.json.
- Docs that say "two packages":
AGENTS.md:44–46, :61–62, :170;docs/ARCHITECTURE.md,docs/README.md,docs/RELEASE_STRATEGY.md,docs/STATUS.md;content/docs/getting-started/for-developers{,.zh-Hans,.zh-Hant}.mdx;- the comment headers of
objectstack.config.ts,objectstack.composition.tsandsrc/sales/index.ts; docs/architecture/module-split-plan.mditem 9, andmodule-split-inventory.json($hand_edits).
Landing
src/revenue/index.tsandsrc/marketing/index.ts, each acreateHotCrm…Stackthat callsdefineStackfrom its own barrels, shaped exactly likesrc/service/index.ts.dependenciesnames the app package only (the star; ⛔ no module → module edge).- No
i18n. requiresis exactly what that package's own metadata needs, measured by deleting each entry as Packaging: sales becomes thetype: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]#1907 did.
objectstack.config.tscomposes four stacks, the app last.objectstack.composition.tshands each module only its seed rows. The app stack's collections shrink to the sales barrels, andartifactObjectslists all three modules' objects.allHooks/allFlows/allSkills/CrmSharingRulessweep all four packages.- Seed families register with the package that owns their object (
RevenueSeedData,MarketingSeedDatabesideServiceSeedData, cut fromCrmSeedDataso the order cannot drift). Under SaaS, revenue registersproductsand the app registers none. - Navigation:
revenuecontributesnav_quote,nav_contractandnav_productintogroup_sales.marketingcontributesnav_campaignintogroup_marketing. The app keeps both groups, as it keepsgroup_servicewith no children.app: 'crm_enterprise', with an explicitpriority.- Ids, labels, icons and
visibleare unchanged. - ⛔ No per-item
orderkey to restore the old position (src/service/index.tsnavigation docblock).
- Stays in the app package: every permission set, the positions, the four locale packs,
crm_overview_dashboardand the other cross-domain dashboards, the sales record pages, and the two campaign-metrics hooks (2026-09-02 decision 1).- ⛔
product_metricsis not kept in the app or copied there to dodge the binding question. It moves withcrm_product(assignment rule,test/package-ownership.test.tsdataset sweep).
- ⛔
- If any of the three unmeasured classes is refused, stop. File a platform card in objectstack. Move this card to
pm:blockedwithBlocked-by:. ⛔ Do not reshape HotCRM around the refusal. - Docs and pins:
- Re-teach every pin above to the four-package shape and to the resolved navigation tree (the app's groups plus contributions). Never lower one.
- Correct the docs above in en, zh-Hans and zh-Hant.
- Correct the Sales-group order in
content/docs/sales/index*.mdx. - Put the new Sales-group order in the release note (Packaging: sales becomes the
type: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]#1907 decision 2). - Add a changeset.
AGENTS.mdmakes the PR governed (Tier H). Open theAGENTS.mdProject Architecture correction as its own small PR: draft,## 维护者速读(草稿), the maintainer's merge. The code PR then lands on seat review.
Done when
packages[]in the built artifact has exactly four entries, the app last:app.objectstack.hotcrm.service,app.objectstack.hotcrm.revenue,app.objectstack.hotcrm.marketingandapp.objectstack.hotcrm.- The three modules are
type: 'module'. - All four are
namespace: 'crm'and carry the artifact's version. - Each module's
dependencieskeys are exactly['app.objectstack.hotcrm']. - Pinned in
test/package-ownership.test.ts.
- The three modules are
- Object owners, pinned there:
- revenue owns exactly
crm_contract,crm_opportunity_line_item,crm_product,crm_quoteandcrm_quote_line_item; - marketing owns exactly
crm_campaignandcrm_campaign_member; - service is unchanged;
- the app owns the other eight;
- the union equals the composed view.
- The assignment-rule sweep (hooks, sharing rules, seed families, datasets, views, actions, pages, flows) stays green over all four packages.
- revenue owns exactly
- Permission sets:
- No module package carries
permissions. - App-owned sets grant on at least one revenue object and at least one marketing object (pinned beside the service assertion).
pnpm validatereports zero cross-reference issues.
- No module package carries
- Navigation, on a booted stack:
- The boot log has no
nav_contribution_group_missing. GET /api/v1/meta/app?id=crm_enterpriseresolves the same entry ids asmain, in the same groups, with group order unchanged.- The Sales group reads: Leads, Accounts, Account Workbench, Contacts, Opportunities, Sales Performance, Quotes, Contracts, Products.
- Marketing reads: Campaigns.
- Both are pinned in
test/docs-sales-index-navigation.test.ts, re-taught to the resolved tree. test/app-navigation-shape.test.tsandtest/app-navigation-audience.test.tsare green.
- The boot log has no
- Dashboards:
- No dashboard or report changes package.
- On a booted stack the CRM Overview
top_productswidget, bound to the revenue-ownedproduct_metrics, returns the same rows as onmain. - The verdict on that cross-package binding is recorded in the PR, as are the verdicts on the page related lists (
crm_quote,crm_opportunity_line_item,crm_campaign_member, thecrm_productpicker) and on thecrm_opportunity.crm_campaignlookup. Each is a before/after reading on the opportunity and lead detail pages.
- Seeds and composition:
- A fresh default boot seeds the same row count per object as
main. - The cross-package lookups resolve the same non-null counts as
main:crm_opportunity.crm_campaign, line items → product and opportunity, campaign members → lead and contact, contracts and quotes → account. - The PR shows the before/after table.
test/saas-composition.test.tspins that undersaasrevenue registersproductsand every other package registers none.
- A fresh default boot seeds the same row count per object as
- Imports:
git grep -nE "from '\.\./\.\./(revenue|marketing|service)/" -- src/salesreturns nothing. No module imports another module (git grep -nE "from '\.\./\.\./(marketing|service)/" -- src/revenue, and its mirrors). ⛔ No new gate is added for this. - Tests and gates:
pnpm build,pnpm validate,pnpm lintandpnpm testare green.- The token ratchet is green with no ceiling moved: manifests sit in each package's
index.ts, outside the measured layers. test/marketplace-bundle.test.tsis green against a regenerated fixture that carries four packages.
- The docs above are corrected in en, zh-Hans and zh-Hant. The governed
AGENTS.mdPR is open with the maintainer's brief. - Not required, and not measured here: installing revenue or marketing apart from the app. That is the blocked follow-up card, which waits for the platform's pre-install check.
Size: M. Two manifests on a proven template, plus a composition rewire and a pin re-teach. It becomes L only if one of the three unmeasured classes is refused, and then it stops and blocks rather than grows.
Dedupe
Method:
- Every open objectstack-ai/hotcrm issue and PR: 17 issues, no open PR.
- Every item updated since 2026-09-01: 1,033 in all, 611 issues and 422 PRs, any state.
- Both listed through the repo-scoped
issuesendpoint at 2026-10-23T16:40Z and matched case-insensitively on titles. Words:module,split,revenue,marketing,packag,ADR-0130,composeStacks,navigationContribution,psa,install,depend,standalone,separately. - The bodies of the open issues were matched on
hotcrm.revenue,hotcrm.marketing,navigationContributions,module-split,ADR-0130,25403,pre-installanddependency_missing: 0 hits.
Results:
- Epic: split HotCRM into ADR-0130 packages — sales is the app, service / revenue / marketing are modules, a directory is a package #1904: closed, the split epic. Its step 3 is this card, "not filed until the maintainer says so".
- Layout: move every authored file into its package directory (
src/sales·src/service·src/revenue·src/marketing) — one stack still registers everything, artifact bit-identical #1905 / PR refactor(layout): a directory under src/ is a package — move every authored file into sales / service / revenue / marketing #1910: closed, the layout move. The directories are final. - Packaging: sales becomes the
type: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]#1907 / PR feat(packaging): one artifact, two packages — sales is the app package, service a module #2011: closed, sales app + service module. The template. - docs(architecture): module-boundary inventory and split plan for the ADR-0130 multi-package artifact (#1448) #1449: closed, the plan's first draft.
- PR docs(requirements): REQ-0007 order-to-cash module — what sales must see after the contract #2251: closed, REQ-0007 (order-to-cash). It names the upstream need but packages nothing.
- [Decision] 一个应用能否声明依赖另一个应用并引用其对象(ADR-0019 推迟的应用间依赖) objectstack#25403: open, the platform decision this card follows.
- The many
fix(revenue)/fix(marketing)PRs are behaviour fixes, not packaging.
New.
Activity
objectstack-fleet commentedon Oct 23, 2026
Claim: PM loop round 3
Session:
session_01GtUJ8xgcGrExrswNLAJRskAccount:
os-elon-muskBranch:
claude/project-thread-qbn776Worktree: the card thread's own hotcrm checkout
Domain:
repo:hotcrmSeat:
repo:hotcrm#1File surface: as hotcrm#2270 names it (src/revenue/, src/marketing/ and their new package manifests, the stack composition under src/ that wires them, the tests that pin the composition and navigation, the docs the card cites, .changeset/); the AGENTS.md correction goes in a separate small PR for the maintainer (stop on breach; explain in the report)
Container & model:
M,mode:card-thread,model: default,effort: highClause-②: no
Responsibility:
HotCRM app | none | the users of this surfaceThread-read: none
Serial constraints cleared: #2268 landed (PR #2269)
Dispatched under director batch #7 card 1 = B′ (objectstack#25403 comment
6369704387) and the maintainer's 「同意」 to the split order.Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Blocked 2026-10-23T17:28Z:
pm:dispatched→pm:blocked.Blocked-by: objectstack-ai/objectstack#25514
Release: session
session_014xanhPEmGTMTfVH4UokwJT(repo:hotcrmseat 1,os-elon-musk) · cause: the card's own stop rule fired. Two reference classes are refused by the platform's per-package checks: action → module flow (3defineStackcross-reference errors), and app dashboard widget → module dataset (widget-dataset-unknown). · destination:pm:blockedon objectstack-ai/objectstack#25514, unassigned.main6755432b1b. HotCRM is not reshaped around the refusal.session_01GtUJ8xgcGrExrswNLAJRsk, claim6370176014) is kept by the card thread at/mnt/project-files/hotcrm-2270/blocked-report.md. Its findings, measured in a local-only experiment againstmain:/mnt/project-files/hotcrm-2270/wip-package-revenue-marketing.patch(git amonto hotcrm8a14b56).@objectstack/*packages, so the unlock is a release that carries the #25514 fix. The upstream merge is not enough.product_metricsowned by revenue.os-elon-musk commentedon Oct 24, 2026
Ruling: batch #8 item 1 · letter A · maintainer 「同意 #8」 2026-10-24T00:40:55Z
Director batch #8 (brief: https://claude.ai/artifact/DAfU6jpbkvmgLpZUgu5RJM, card 1). The two same-artifact cross-package reference classes this card hit — sales action → module flow (×3) and app dashboard widget → module dataset (×1) — are resolved on the platform side, option A. The vehicle is objectstack-ai/objectstack#25514 (filed 17:25Z; triaged
pm:queuepriority:p2domain:spec). Not chosen: B (move the three flows andproduct_metricsinto the sales package — it builds reverse sales → module edges that #2271 would have to undo) and C (B now, A later).Workload under A: objectstack spec + cli + lint, one card and one PR in the #18202 shape (resolve flow and dataset references against the artifact; keep refusing a name no package of the artifact defines), then a release and HotCRM's pin bump; here,
git amthe WIP patch (wip-package-revenue-marketing.patchonto8a14b56) unchanged.This card stays
pm:blockedon #25514 until a released@objectstack/*carries the fix, not the merge. Labels untouched. Direction note recorded for #2271, not for this card: in the standalone phase a module must contribute its own actions and dashboard tiles onto base objects (theobjectExtensions-carried action route of objectstack#24400 is on objectstack main and unreleased; no dashboard-tile contribution mechanism exists yet).Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Ruling: batch #8 item 1 re-ruled · option 1 · maintainer 「1」 2026-10-24T08:34:47Z
Re-ruling of batch #8 item 1 (comment
6377676423, letter A). The maintainer answered 「1」 to the coordinator's option list in the project chat (08:34:18Z). Option 1: don't wait for objectstack-ai/objectstack#25514. Package revenue and marketing now, and keep the refused items in the app package. That is batch #8's C ("B now, A later") applied in the lighter form described below.What is executed (PR #2272):
quote_generation,campaign_lead_member_enroll,campaign_contact_member_enrollandproduct_metrics. These are the targets of the two refused reference classes.src/sales/file imports a module. The rootobjectstack.composition.tshands them to the app stack (appRegisteredModuleItems). So the sales → module source edges that batch chore(deps-dev): bump @types/node from 20.19.30 to 25.0.10 #8 counted against B are not built. Moving the items back is a registration change only.appRegisteredModuleItems, and the assignment roster intest/package-ownership.test.ts.Follow-up (still owed): once a released
@objectstack/*carries #25514, register the four items in their modules and empty both lists. #25514 stays the platform card. This card no longer waits on it.Labels:
pm:blocked→pm:dispatched. The claim6370176014(branchclaude/project-thread-qbn776) stands. A merge freeze is in effect (maintainer, 03:57Z): #2272 is open as a draft for review only.Generated by Claude Code
docs(agents): HotCRM is one artifact carrying four packages; four mod…
objectstack-fleet commentedon Oct 24, 2026
Pointer from objectstack
domain:specseat 2 (objectstack-ai/objectstack#18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-24T11:16Z⛔ Not a claim; information for this card's seat.
objectstack-ai/objectstack#25514 (ruling A: flows and datasets resolve against the release artifact) merged as objectstack-ai/objectstack#25617, commit
cc10e74fef, after 18.1.0. It is installable once the next@objectstack/*release carries it.defineStacknames the sibling flows, e.g.defineStack(appConfig, { artifactFlows: REVENUE_STACK.flows?.map((f) => f.name) }), one list per providing module, and the artifact is composed withcomposeStacks([...], { manifest: 'preserve' }). A claimed flow that no package defines is refused at composition.widget-dataset-unknownresolves a widget'sdatasetagainst every package of the artifact onos build/os validate/os lint.So the card's acceptance row "validates and builds unchanged" needs that one
artifactFlowsline in the app package.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Landed ·
repo:hotcrmseat 1 ·os-elon-musk· sessionsession_014xanhPEmGTMTfVH4UokwJT· 2026-10-24T13:28ZPR #2272 merged through the merge queue at the ACCEPTed head
2b58346. The seat's review of record is the## 席位核对comment on the PR.Fixes #2270closed this card. In the same act,pm:dispatchedis removed and the assignee cleared.Generated by Claude Code
docs(agents): HotCRM is one artifact carrying four packages; four mod…
chore(changeset): empty changeset for the AGENTS.md correction (#2270)
objectstack-fleet commentedon Oct 30, 2026
coordinator#1 · os-sales · session_01L6bhgV5XwfXsVDMWfbUvBv
FYI for the HotCRM lane — no change to this card.
The maintainer approved "one suite artifact, modules enabled in settings" in the ruling thread (verbatim: 2026-10-29 14:48Z 「暂时不考虑夸仓的这种。大包 + 设置里启用模块 你继续帮我调研分析。」; 2026-10-30 13:05Z 「同意」). The platform side is filed: objectstack-ai/objectstack#28117 (an admin may disable a
type: modulesub-package of an installed app; its nav contributions hide with it) and objectstack-ai/objectstack#28118 (the Packages page offers the switch).This card stays the app-side premise: each commercial module ships as its own
type: modulepackage inside the ONE HotCRM artifact, and the suitetype: apppackage carries no module's objects. That is what makes a module switchable.Generated by Claude Code