Repository navigation
feat(packaging): one artifact, two packages — sales is the app package, service a module - #2011
Conversation
…rtifact
The service directory becomes app.objectstack.hotcrm.service, a type:module
package with its own defineStack (src/service/index.ts), dependencies on the
app package and three navigationContributions carrying nav_case,
nav_knowledge, nav_service_dashboard, nav_my_cases and nav_report_sla into the
groups the app keeps. src/sales/index.ts is the app package's defineStack
factory; its permission sets stay whole and grant on the module's objects
through DefineStackOptions.artifactObjects. objectstack.composition.ts builds
the two package stacks and objectstack.config.ts composes them with
composeStacks(..., { manifest: 'preserve' }), the app last.
The suites read test/helpers/composed-stack.ts (the same two stacks under the
platform's flattening composition), since a multi-package artifact carries its
collections only in packages[]; the navigation pins read the served tree
(SchemaRegistry.applyNavContributions) rather than the authored one.
Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3
Co-authored-by: Claude <noreply@anthropic.com>
…et for #1907 AGENTS.md said `objectstack build` reads `src/docs/` and no other path. With the artifact carrying packages[], the build also collects a `src/<dir>/docs/` whose name is a package id or its last segment, onto that package's body — measured: `src/service/docs/` reaches the service module, `src/sales/docs/` answers to no package and is reported as `docs/uncollected-directory`. The four pages stay at the top of `src/`. The patch changeset describes the two packages and the one navigation move (My Cases last inside My Work). Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3 Co-authored-by: Claude <noreply@anthropic.com>
…old item ownership Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3 Co-authored-by: Claude <noreply@anthropic.com>
The service manifest's version comment no longer claims `changeset version` moves it (it does not touch .ts files); the declared-versions pin holds it. The app factory's artifactObjects note carries the measured refusal without the option (18 grants) and the measured scope of composeStacks' verification (a grant on an object no package defines is refused even when listed). The quoted per-stack trigger refusal is the 17.7.0 wording. Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3 Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
… move src/docs A boot of the two-package artifact logs a MetadataPlugin WARN that the four src/docs pages are claimed by no package body and tells the author to rebuild them into one. The platform's builder put them there by its own rule, the owner reported is the app package, and no directory under the ruled layout can carry them into its body; the note keeps the next agent from acting on it. Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3 Co-authored-by: Claude <noreply@anthropic.com>
… shape now states two packages The surface amendment on #1907: each present-tense claim that objectstack.config.ts is the one defineStack, carries the manifest, the requires or the version, or is handed the collections, is rewritten to the two package stacks it composes — the product docs in all three locales (extending-objects, ai-skills, fork-hotcrm, integrations, cubes, customization/index, sandbox-and-releases, security-and-compliance), the release runbook (RELEASE_STRATEGY, CONTRIBUTING, MAINTENANCE, DEPLOYMENT), code_examples, feature-inventory, README, .github/tasks/new-package.md, scripts/wow1-live-schema.sh, the source-hygiene surface note, the src barrel and seed comments, and the test docblocks and messages that described the registration path. Dated records (audits, measurements, the split plan's recorded decisions, generic single-package marketplace guidance) are left as they are. The seed comments carry a new measurement: on a fresh boot of the two-package artifact every seeded case resolves its account (38/38) and every seeded event that names a case resolves it (2/2). Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read: card #1907 (body and all 9 comments, rounds 0 and 1 dev reports Check-runs on ① Derived judgmentsThe governed hunk (
Does any rule now contradict another, ADR-0130, or the layout? No. Rule 2 (imports only own directory or Does the PR change what an agent following Anything published changed without disclosure? No. Head-of-record consistency checks. The six commits carry the model-free trailer pair only ( ② Semver levelChangeset ③ Boundary flagsRound 0 dev report
Round 1 dev report Flags I raise, none blocking:
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读
改了什么
对象名、数据表、接口地址一个都没改,客户看到的还是同一个 HotCRM。服务相关的五个菜单项改由服务模块"挂"进主应用菜单。唯一可见变化:「我的工单」从「我的工作」分组的第 4 位移到最后一位。另外,所有仍描述旧结构的文档(产品文档三种语言、发版手册、开发者文档,共 57 个文件)已一并改写。 为什么改 风险与代价(含回滚)
席位意见
你要做的(一个动作) Generated by Claude Code |
Fixes #1907
Packaging step 2 of ADR-0130:
src/sales/becomes thetype: apppackage andsrc/service/atype: modulepackage inside one release artifact. Rebuilt onmain(c529de2b,@objectstack/*17.7.0) with the blocked branchbe11c07as a read-only reference. No object, field or behaviour changes; no file moves.[MetadataPlugin]WARN about the foursrc/docs/*.mdpages. See Known gap below. Everything else is measured green.What this PR does
src/service/index.ts—app.objectstack.hotcrm.service,type: 'module',namespace: 'crm',version: '3.1.0',engines.protocol: '^17.7.0',dependencies: { 'app.objectstack.hotcrm': '^3.1.0' }, its ownrequires: ['automation', 'triggers', 'analytics', 'sharing'], and threenavigationContributionsintocrm_enterprise(group_service:nav_case,nav_knowledge,nav_service_dashboard;group_work:nav_my_cases;group_insights:nav_report_sla). It registers its own barrels' items by name (3 objects, 8 hooks, 2 views, the case page, 7 flows, 8 actions,case_metrics, 3 reports, the Service dashboard, the case-triage skill, 3 sharing rules, the 2 case email templates) plus the seed families the root hands it.src/sales/index.ts—createHotCrmAppStack(collections, { artifactObjects }): the app manifest,requires,i18n; collections handed in by the root becausesrc/revenue/andsrc/marketing/are still registered by the app package and asrc/sales/file may not import them. Permission sets stay whole here (ADR-0130 2026-09-02 addendum) and grant on service objects throughDefineStackOptions.artifactObjects(objectstack#18202).objectstack.composition.ts— the app package's collections (appObjects,appHooks,appFlows,appSkills,appSharingRules, …), the artifact-wide unions the suites sweep (allHooks,allFlows,allSkills,CrmSharingRules), the seed split (ServiceSeedData/AppSeedDatacut out ofCrmSeedData), theHOTCRM_COMPOSITIONknob (moved here from the config, unchanged), and the two built stacksserviceStack/appStack.objectstack.config.ts—export default composeStacks([serviceStack, appStack], { manifest: 'preserve' }), the app last, nothing else exported.src/sales/apps/crm.app.ts— the five service entries leave the app'snavigation;group_servicestays as an empty container the module fills.test/helpers/composed-stack.ts: the same two stacks under the platform's flatteningcomposeStacks(what ~100 suites read, since the artifact no longer carries flattened collections), andservedApps()/servedApp(): the navigation folded by the platform's ownSchemaRegistry.applyNavContributions. Eight suites taught the contribution channel (floors unchanged);docs-declared-versionstaught every package of the artifact;saas-compositiontaught the per-package seed split; newtest/package-ownership.test.ts.AGENTS.mdProject Architecture (line 44 now true for sales + service, and says revenue/marketing are directories of the app package), thesrc/docs/note (measured, see below) and its closing sentence that the boot's[MetadataPlugin]WARN about the four pages is not an instruction to move them (the platform's own builder put them there; reported upstream),docs/README.md,docs/ARCHITECTURE.md,docs/STATUS.md,content/docs/getting-started/for-developers*.mdx, the My Work row ofquick-tour*.mdx(My Cases last), and — round 1 — every other file whose present-tense text still stated the one-defineStackshape (see Round 1 below)..changeset/1907-sales-app-service-module.md—patch: the marketplace listing gains a package row and My Cases moves inside My Work, so the consumer surface changed; the release note states the move.What the artifact really emits (acceptance 1, re-read against the 2026-09-22 addendum)
dist/objectstack.jsontop-level keys are exactlymanifest,packages,i18n,docs— no flattenedobjects/views/flows/ … besidepackages[].manifest.idisapp.objectstack.hotcrm.packages[]slotapp.objectstack.hotcrm.servicemodulecrm_article_feedback,crm_case,crm_knowledge_articleapp.objectstack.hotcrmappEvery object keeps its FQN (same names,
crmnamespace). Ownership is read off the bodies and offGET /api/v1/packages(below), and pinned bytest/package-ownership.test.ts(exactly two packages, the app last; one owner per object; every hook / sharing rule / seed family / dataset / view / action / page / flow authored against a service object ships in the service module; permission sets only in the app package; the composed view equals the bodies collection by collection). The one exception to "everything is in a body" is the top-leveldocs[].The PM's five mechanism assumptions, measured
artifactObjectsonly.pnpm validateexit 0, "Running author-time rules per package (2)" passes (theattendee_contacts→crm_contactrefusal is gone). The #18202 fix is OPT-IN: withartifactObjects: []the app stack refuses exactly the 18 grantsbe11c07hit (defineStack cross-reference validation failed (18 issues), 6 each oncrm_case,crm_knowledge_article,crm_article_feedback). The option is verified, not trusted: a grant oncrm_ghostlisted inartifactObjectsbut defined by no package →composeStacks artifact cross-reference validation failed (1 issue). No new platform refusal.lint --strict— 0 warnings.pnpm lint: 0 errors, 0 warnings, 18 suggestions, line-for-line the same asmainbut for array indices. Notranslation-target-unknown(objectstack#18203 is in 17.7.0).be11c07's decisions re-checked on the new tree. (a) Seed rows registered by the owning package: kept. New on 17.7.0:artifactObjectswidensdata[]too, so route (a) is no longer refused — the platform accepts a service family registered by the app package (pnpm validateexit 0 withknowledgeArticlesmoved), which is whypackage-ownershippins the family's owner. (b)requiresper stack: required — deleting the service module's line gives 3defineStack trigger capabilityrefusals. (c) Factories:createHotCrmAppStackis needed (revenue/marketing);createHotCrmServiceStacktakes onlydata, so the knob is resolved once. (d)src/sales/interfaces/index.ts: kept — header-only (75 bytes), imported by nothing; dropping it means editingcontent/docs/customization/index*.mdx×3 anddocs/architecture/module-split-inventory.json, outside this card's file surface.AGENTS.md:44now reads "one ObjectStack release artifact carrying two packages …src/revenue/andsrc/marketing/are package DIRECTORIES that are not packages of the artifact yet".Acceptance
packages[]of two entries, FQNs kept, ownershipwritable: false; nonav_contribution_group_missing; no ownership diagnostic[MetadataPlugin]WARN (Known gap)mainnav_my_casesmovepnpm verifygreen; pins keep their floors;src/sales/token reading5157d610; ✅ ablation 2; business semantics unchangedpatch(consumer surface changed)Browser walk (acceptance 3)
Both sides booted the same way:
objectstack dev --fresh --seed-admin --no-watch --compile(fresh DB, from source), head on port 4870 at9b3b6571,mainon 4880 atc529de2b, Chromium/opt/pw-browsers/chromium, signed in as the seeded admin. Commits after9b3b6571change comments, tests and docs only (no non-comment line insrc/or the two root files).mainGET /api/v1/packages(hotcrm rows)app.objectstack.hotcrmapp,writable: false, 18 objectsapp.objectstack.hotcrmapp,writable: false, 15 objects ·app.objectstack.hotcrm.servicemodule,writable: false, 3 objects (listed after the app: registration order followsdependencies, not the slot)GET /api/v1/meta/app)nav_my_cases4th → last inside My WorkGET /api/v1/meta/doc)/api/responsesThe two zeros (Contacts, My Calendar) are the walk's
tbody trprobe on both sides: My Calendar is a calendar view; the contact list page showed no grid rows to the probe onmaintoo. Seeded rows resolve their lookups across the package boundary in both directions although each package's seeds replay from its own body — measured overGET /api/v1/dataon a fresh boot of the branch (round 1, metadata identical to the head): 38/38 seeded cases resolvecrm_accountto a real account, 2/2 seeded events that name a case resolverelated_to_caseto a real case.Cross-package measurements (acceptance 6, on the 17.7.0 pin)
artifactObjects; ❌ refused without itattendee_contactsrecord-picker →crm_contact(objectstack#18204)case_detailpage related list →crm_tasknavigation→ service object (R3)navigationContributionspriorityorders contributions only; a contributed item is appended after the group's own children (nav_my_caseslast)lint --strictcustomer_360skill namingcrm_case/crm_knowledge_article; R1 lookups intocrm_caseToken gate (
pnpm hygiene:tokens,src/sales/)Business semantics 56,388 — unchanged. Interaction layer 28,136 → 27,966 (the five entries left
crm.app.ts). Authored total 100,067 → 100,165 (the newsrc/sales/index.ts).src/service/authored total 20,478 → 21,493 (itsindex.ts), headroom 507. No ceiling moved; "source token ratchet clean".Gates (head
5157d610)pnpm verify→ exit 0 (os-verify-lockVERDICT command-exit 0, held 211s):✓ Validation passed· typecheck exit 0 · lint 0 errors / 0 warnings / 18 suggestions ·✓ i18n lint gate: 0 i18n/missing-* issues·✓ source hygiene clean·✓ source token ratchet clean·✓ Build complete(8 author-time warnings, the approver-staffing advisoriesmainhas too) ·Test Files 179 passed (179),Tests 3832 passed | 1 skipped (3833).Reverse verification (each mutation landed on disk via
ablation-replace.mjs, restored to the HEAD blob,git diff HEADempty)[appStack, serviceStack]→package-ownership: 2 red ("the app last", "identifies as the app"). Predicted red, observed red.app: 'crm_nowhere'→ 19 red in 6 files (action-references2,docs-analytics-vocabulary4,docs-quick-tour-navigation10,docs-service-index-analytics1,docs-setup-navigation-names2,i18n-references1) — exactly the set that went red before the pins were taught, so the floors hold through the contribution channel.view-referencesandauthorization-coveragestay green, as predicted: fewer entries cannot dangle.ServiceSeedData = [cases](knowledge seeds fall to the app package) →pnpm validateexit 0 (the platform accepts it),package-ownership+saas-composition: 2 red.Round 1 — the pre-#1907 shape, swept
The claim's surface was amended (#1907, Claim amendment (file surface)) to every file in which a sentence asserted the PRE-#1907 shape as current — one
defineStackinobjectstack.config.ts, or the collections handed to it as the registration path. The hit list was re-taken onf294875d(grep forobjectstack.config,defineStack,composition.ts, the registration-list names, "single / one defineStack", "four packages" and their Chinese spellings, plus every removed export name such asallDatasets), and every hit classified by hand.Rewritten (present-tense claims about the current shape) — 57 files:
customization/extending-objects(hooks and flows register through their package's list —appHooks/appFlowsinobjectstack.composition.ts,serviceHooks/serviceFlowsinsrc/service/index.ts),customization/ai-skills(appSkills/serviceSkills),marketplace/fork-hotcrm(step 2 edits the two package manifests; step 4 drops the service stack from the composition and the config),guides/integrations(no package listswebhooksinrequires; the hand-copied roster is gone),analytics/cubes(allDatasetsno longer exists — datasets register through their package's stack),customization/index(one artifact, two packages),administration/sandbox-and-releases(seed data the packages register),reference/security-and-compliance(pluginsbelongs on the app package's stack).docs/RELEASE_STRATEGY.md(release unit carries two packages; the manifestversionto match by hand lives insrc/sales/index.tsandsrc/service/index.ts),CONTRIBUTING.md,docs/MAINTENANCE.md(identity, protocol bump, releasing),docs/DEPLOYMENT.md,docs/developers/code_examples.md,docs/feature-inventory.md(ADM-015's source),README.md(badge link and layout tree),.github/tasks/new-package.md(step 4 now scaffolds a package stack the waysrc/service/index.tsis built).scripts/wow1-live-schema.sh,scripts/lib/source-hygiene-surface.mjs.src/*/data/index.ts,src/*/flows/index.ts,src/sales/objects/hooks.ts,src/sales/objects/index.ts),src/sales/profiles/tenant-admin.profile.ts, and the two seed modules whose "the root config replays them in order" is replaced by the measurement above (src/sales/data/activity.seed.ts,src/service/data/service.seed.ts).flow-registration-completeness,registration-list-completeness,helpers/registration-lists,docs-metadata-counts,docs-role-hierarchy,docs-src-tree-paths,import-mappings,persona-copy,demo-staffing, ande2e/opportunity-win-loss-capture.spec.ts.Left as they are (each read, none asserts the old shape as current):
administration/sandbox-and-releasesandreference/security-and-compliance("checked claim by claim against … spec 17.0.0-rc.6"),src/service/objects/_case-assignment.ts("measured against the full shipped stack"),test/docs-declared-versions.test.ts's [17.0-rc2验收] objectstack.config.ts 的 engines.protocol 仍是 ^17.0.0-rc.1,与 objectstack.manifest.json(rc.2)及 build 产物不一致 #728 history,test/sharing-coverage.test.ts's table history,docs/architecture/module-split-plan.mdandpsa-module-plan.md(recorded decisions and plans),README.legacy.md,CHANGELOG.md,.changeset/*,docs/archive/.objectstack.config.tshas noserverblock" (reference/performance-and-limits), "carries noauthblock" (reference/security-and-compliance), "metadata registered byobjectstack.config.ts" (customization/api-reference), "registered throughobjectstack.config.ts" (analytics/cubesopening), "objectstack.config.tsnever imports this file" (scripts/demo-staff.ts), "not registered inobjectstack.config.ts" (src/sales/sharing/demo-staffing.ts), the kernel suites' "boot … overobjectstack.config.ts" (they boot the real artifact),package.json/tsconfig.json/.github/labeler.ymlentries, and the "four packages" of the layout (customization/ui-extensions, the token-gate tests).marketplace/manifest-and-versioningandmarketplace/publishing-your-first-appdescribe a single-package app (app.acme.crm) declaring its manifest in its ownobjectstack.config.ts, which remains a valid shape for such an app.Known gap — the docs residual WARN (acceptance 2)
The four items are
crm_admin,crm_overview,crm_sales,crm_servicefromsrc/docs/.os buildwrites flatsrc/docs/*.mdto the artifact's top level by its own rule, and the runtime then flags any top-level item of a multi-package artifact. The owner it reports is the app package — the same owner as onmain— and all four docs are served. The remedy it prints cannot be followed here: a per-package docs directory is collected only when its name is the package id or the id's last segment, andsrc/sales/answers to neither (app.objectstack.hotcrmends inhotcrm); measured:src/sales/docs/→docs/uncollected-directory,src/service/docs/→ collected onto the service body. Per AGENTS.md §2 this is reported for the platform, not routed around (nosrc/hotcrm/directory, no rename). Whether this WARN holds the merge is the maintainer's call.Platform findings — filed upstream by the seat (no workaround here)
os buildwarns that its flatsrc/docspages are "claimed by no package body" — the CLI puts them at the top level by its own rule, and the warning`s remedy cannot be followed objectstack#22190 · class b · reach:pnpm devboot log, every boot — the docs residual above. Seam: specdocs(package-owned) → clicompileflat-docs collection writes top-leveldocsbesidepackages[]→ runtimeMetadataPluginresidual WARN. Dedupe words: flat src/docs, top-level metadata item, package bodies, residual, ADR-0046, ADR-0130 D4.os validate/os buildcapability preflight reads only the artifact top-levelrequires— in a multi-package artifactrequiresis package-owned, so an unprovidable capability passes with exit 0 objectstack#22189 · class b · reach:os validate/os buildexit 0 on a wrong answer — the installable-provider preflight readsrequiresfrom the artifact's top level only, which a multi-package artifact no longer has. Measured:requires: [..., 'ai']in the service module →pnpm validateexit 0,pnpm buildexit 0; the same token inmain's single-package config → both exit 1 ("Capability provider check failed … @objectstack/service-ai … not available in the open edition"). Also drops thehierarchy-securityadvisory linemainprints. Seam: specrequires(package-owned) → clivalidate/compilepreflightRequiredCapabilities({ requires: config.requires }). Dedupe words: preflightRequiredCapabilities, requires, packages[], missed reader, capability provider check.Acceptance notes
module-split-plan.mditem 6's premise "the platform reads package docs fromsrc/docs/only" no longer holds for a module whose directory is its id's last segment (src/service/docs/would be collected) — relevant to wherecrm_service.mdlives once the app side is solved upstream. Not moved here (this card moves no files).HOTCRM_COMPOSITION=saaswas exercised bysaas-composition(service package registers no rows), not booted.picklists; after this lands it goes into the app stack inobjectstack.composition.tsrather than the config.维护者速读(草稿)
改了什么
HotCRM 现在仍是一个产品、一个安装包,但包里分成了两个"模块包":HotCRM 主应用(销售,含客户、联系人、线索、商机等)和 HotCRM Service(服务:工单、知识库、文章反馈)。客户看到的还是同一个 HotCRM;对象名、数据表、接口地址一个都没改。服务相关的五个菜单项改由服务模块"挂"进主应用菜单,唯一可见变化:“我的工单”(My Cases) 从“我的工作”分组第 4 位移到最后一位。
为什么改
这是 2026-09-14 拆分裁决的第二步(先拆 sales 和 support)。拆开后,Studio 能按模块分组展示,服务模块有了自己的边界和预算,后面拆收入、营销、PSA 都照这个模式加目录即可。上一轮卡在平台的权限校验上,17.7.0 平台已提供正式开关(
artifactObjects),本轮无需任何绕路。风险与代价(含回滚)
风险低:浏览器对比 main,菜单 33 项、17 个列表行数、4 个记录页、5 个看板、审批收件箱、0 页面错误全部一致。代价有二:启动日志多出一条平台告警(4 篇应用文档挂在包外——平台自身的生产方与读取方不一致,已立上游卡 objectstack#22190);
pnpm validate对模块包里的能力声明少了一项预检(同为平台缺陷,已立 objectstack#22189)。所有仍描述旧结构的文档(产品文档三种语言、发版手册、开发者文档)已在本 PR 第 1 轮一并改写,发版时版本号改在两个包的清单里。回滚:直接 revert 本 PR,数据与对象不受影响。席位意见
你要做的(一个动作)
决定这条启动告警是否阻塞合并:若接受(文档归属仍是主应用,与现在相同),合并本草稿 PR;若不接受,保持草稿,等平台修复后再合并。
Generated by Claude Code