Repository navigation
[epic] hotcrm ADR-0130 拆包的上游依赖集 —— 一条代码门禁(#18202) + 一次发版,其余五张是作者体验 #18215
Description
Tracking card for the upstream half of hotcrm's ADR-0130 package split. The downstream epic is objectstack-ai/hotcrm#1904; the blocked packaging card is objectstack-ai/hotcrm#1907, whose branch is built, runtime-proven and waiting.
This card exists because the dependency is a set plus a release, and until now no single carrier named the sequence. Filed at the maintainer's prompt (「objectstack 是否也应该开一个专题卡跟踪」).
The one sentence
hotcrm cannot land two packages in one artifact until a released @objectstack/* line carries the #18202 spec fix alongside the already-merged #17066 lint/cli change. Everything else on this list is authoring ergonomics, not a gate.
Blocking — the release path
| Card | State | Reading |
|---|---|---|
#18202 — defineStack's per-stack cross-reference validation refuses permissions[].objects / data[].object naming a dependency's object |
✅ CLOSED 2026-09-15, merged as b8ec127b4 |
packages/spec/src/stack.zod.ts:2021, if (objectNames.size === 0) return errors; — present on origin/main, identical in the published dist; that validator has no dependency awareness anywhere. Fix PR #18212 delivered and ACCEPTed by the PM 2026-09-14 (draft, final head b0072379, 4 commits / 4 files, RED 0, CI still running). DefineStackOptions.artifactObjects widens the two ARTIFACT-SCOPED classes to resolve against the artifact, and composeStacks gained an ARTIFACT pass — measured necessary, since composeStacks ran no cross-reference validation at all before this PR. hooks[].object and app navigation objectName stay refused (ADR-0130 §1.5). This WAS the only upstream code gate, and it is now closed. Four rounds of at-tier contract review (two FAIL, then PASS, then a fourth on the merged-forward tree after the PM cleared the gate 19 seconds before moving the head); landed through the queue, squash, single parent. artifactObjects is on main. |
#18204 — per-package author-time rules refuse an action param's record-picker reference to a dependency's object |
✅ CLOSED 2026-09-15 — premise falsified; never a code gate | Fixed on origin/main by f89dd33cc … (#17066): artifactProvidedObjectNames is defined at packages/lint/src/validate-object-references.ts:162 and called at :209. Absent from the published @objectstack/lint@17.4.0 hotcrm pins. PR #18213 added two-tier regression pins plus an ablation; no behaviour change. Contract review PASS (rendered by an at-tier isolated reviewer, adopted verbatim), merged through the queue 2026-09-15T01:18:19Z as fb3c6b4f6 (squash, single parent). The card closed on its Fixes keyword; pm:dispatched and the assignee came off in the same stroke. |
| The release itself | 🔴 the ONLY remaining link | packages/{spec,lint,cli} are all at 17.4.0 on main with a large unreleased changeset backlog. Merged upstream is not available in a pin (hotcrm AGENTS.md, Platform Upgrades). ⇒ the release is the last link, and it has no carrier other than this card. |
Not blocking — filed from the same work, awaiting triage
| Card | Reading |
|---|---|
| #18203 | translation-target-unknown reads apps[].navigation only, so every locale key for a navigationContributions item looks unknown — 15 warnings on hotcrm's branch. Warnings only. |
| #18170 | The ADR-0046 package-docs collector reads <config dir>/src/docs only. Moving a docs directory under a package layout exits 0 and silently drops docs[]. Forced hotcrm's plan item 6 (src/docs/ stays at the top of src/). |
| #18171 | objectstack.config.ts may carry no named export — the build parses the whole config module against the strict stack schema, undocumented. |
| #18163 | Typed hook ctx.api (HookApi / HookObjectApi / HookQuery) — hand-declared in every metadata app today. |
| #18164 | A shared picklist (global value set) metadata kind. |
The open design question this set sits on
#14488 (closed) proposed the mirror of navigationContributions for authorization: a module contributes its own objects' permission grants into app-owned roles. That is exactly the shape #18202's refusal is about, approached from the other side.
Two routes, and the choice is the maintainer's:
- Route A (applied today). ADR-0130's addendum of 2026-09-02 keeps permission sets whole in the app package; spec:
defineStack's per-stack cross-reference validation refusespermissions[].objectsanddata[].objectthat name an object owned by ANOTHER PACKAGE OF THE SAME ARTIFACT — so ADR-0130's co-owned artifact and the "permission sets stay whole in the app package" addendum cannot both hold once the app package owns objects #18202 is fixed so a stack may grant on a dependency's object. hotcrm's branch lands unchanged. - Route B. Re-rule the addendum so a module ships its own permission sets, which needs 按包组合授权:module 把自己对象的 permission 授权贡献进 app 拥有的角色(类比
navigationContributions)—— ADR-0130 分拆第二阶段,不挡发版 #14488 reopened and decided. hotcrm's branch would be reshaped.
Route A is in force and #18212 implements it. Recorded here so route B is not re-derived from scratch later.
be11c07), the failing stack is the type: app package, which declares no dependencies at all; the module declares the edge, and ADR-0116's topological order admits only module → app. So the resolution scope is the artifact, never the declaring package's dependency closure — a closure-scoped rule would refuse the very shape the addendum makes mandatory. The card title has been corrected.
Deliberately left open by #18212 — so nobody rediscovers it
composeStacks preserves one leniency verbatim, pinned in both directions by the new tests: an input stack that declares no objects at all can still contribute a permission grant or a seed reference that nothing anywhere refuses. Closing it would newly refuse compositions that pass today — references served by a plugin outside the composition — so the fix does not touch it, and the reason is now written at the ARTIFACT pass rather than left to be rediscovered.
Two reference classes also stay stack-scoped on purpose: view data.object / mappings[].targetObject, and actions[].objectName. PM ruling: leave them, and do not open a speculative card. Nothing in ADR-0130 or ADR-0086 rules on a view over, or an action bound to, a sibling package's object, and this round measured no consumer that needs it — hotcrm's branch has no sales-side view crossing into a service object. The argument for widening them is recorded in PR #18212's body; whoever first meets a real shape files the card with that shape attached.
The release step — what is actually available, measured
The epic's last link is a release, and it has two shapes. Both are the maintainer's to pull; neither is something a PM seat can trigger.
Full release. pnpm release off the standing "Version Packages" PR. Slow here by design — that PR is force-refreshed on every push to main, and main moves roughly 18 merges a working day.
RC cut — the fast path. .github/workflows/cut-rc.yml exists precisely because the full lane cannot converge on a busy main: it snapshots one main sha and does every downstream step against that frozen value. It is workflow_dispatch-only, environment: release-gated, and a human types the exact version — the file states this is deliberate, under the 2026-08-07 ruling 「版本发布必须是人工的」. So it is available today, and it is a maintainer action.
What an rc would mean downstream, measured rather than assumed:
- hotcrm pins 21
@objectstack/*packages, all at17.4.0, and its AGENTS.md Platform Upgrades requires they be bumped together (version-locked) withspecVersioninobjectstack.manifest.jsonupdated in the same stroke. - hotcrm has pinned an rc zero times in its history (
git log -S'-rc.' -- package.json→ 0 commits). This would be new practice for that repo. - Nothing in hotcrm's upgrade policy forbids it. The gate is practice, not a rule.
- objectstack already carries
check:prerelease-pinsand aprerelease-pin-watchworkflow, so a prerelease pin is a supported state upstream.
⇒ If the maintainer wants #1907 unblocked before the next full release, the RC cut is the lever, and the only new thing it asks of hotcrm is its first rc pin.
Done when
- spec:
defineStack's per-stack cross-reference validation refusespermissions[].objectsanddata[].objectthat name an object owned by ANOTHER PACKAGE OF THE SAME ARTIFACT — so ADR-0130's co-owned artifact and the "permission sets stay whole in the app package" addendum cannot both hold once the app package owns objects #18202 merged. - A published
@objectstack/*line — a full release or an rc — carries it and fix(cli, lint): gate a dangling lookup/master_detail reference, and resolve one across the artifact packages[] #17066. - hotcrm bumps all 21 pins together (plus
specVersion) andpnpm validateonclaude/issue-1907-sales-app-service-modulereports zero cross-reference issues.
Steps 1 and 2 are this card's; step 3 is objectstack-ai/hotcrm#1907's.
Generated by Claude Code
Activity
objectstack-fleet commentedon Sep 29, 2026
Unlock reading: the release this card waited on is on npm.
@objectstack/*17.5.0 carries #18202's fixTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-29T09:10Z. ⛔ Not a claim; no state change on this tracking card.@objectstack/*line carries the spec:defineStack's per-stack cross-reference validation refusespermissions[].objectsanddata[].objectthat name an object owned by ANOTHER PACKAGE OF THE SAME ARTIFACT — so ADR-0130's co-owned artifact and the "permission sets stay whole in the app package" addendum cannot both hold once the app package owns objects #18202 spec fix") is met.@objectstack/spec17.5.0 was published at 2026-09-29T08:09Z.DefineStackOptions.artifactObjects, the spec:defineStack's per-stack cross-reference validation refusespermissions[].objectsanddata[].objectthat name an object owned by ANOTHER PACKAGE OF THE SAME ARTIFACT — so ADR-0130's co-owned artifact and the "permission sets stay whole in the app package" addendum cannot both hold once the app package owns objects #18202 fix, appears in 6 files of itsdist, and in 0 of 17.4.0's (the dark control). spec:defineStack's per-stack cross-reference validation refusespermissions[].objectsanddata[].objectthat name an object owned by ANOTHER PACKAGE OF THE SAME ARTIFACT — so ADR-0130's co-owned artifact and the "permission sets stay whole in the app package" addendum cannot both hold once the app package owns objects #18202 merged after 17.4.0 was cut, so 17.5.0 is the first line that carries it.type: apppackage and service atype: module—composeStacks(…, { manifest: 'preserve' }), navigation contributions, two-entrypackages[]hotcrm#1907 ispm:blockedon exactly this: its reading is 「判据是 PIN 不是合并」, npm above 17.4.0. Its npm half is now met. Its own pin bump is the hotcrm seat's to take.feat(spec)!([finding] release.yml: every main landing between the version-PR merge and the approval queues a new publish deployment, evicts the waiting one, and ships main's head instead of the version commit — ADR-0125 D1's premise does not hold #20613). A red check on the hotcrm bump is measured, not waved through.objectstack-fleet commentedon Sep 30, 2026
Closed
completed: the release this card tracked has shipped both upstream changesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T06:09Z. ⛔ Not a claim, ⛔ not a dispatch.The maintainer answered 「同意」 in the live PM chat with the triage seat to triage's recommendation to close this card now, rather than wait for objectstack-ai/hotcrm#1907.
Measured. Both changes this card's one sentence names are ancestors of the
@objectstack/spec@17.5.0tag commit0f6dcac5e9, and neither is in17.4.0(the control):defineStack's per-stack cross-reference validation refusespermissions[].objectsanddata[].objectthat name an object owned by ANOTHER PACKAGE OF THE SAME ARTIFACT — so ADR-0130's co-owned artifact and the "permission sets stay whole in the app package" addendum cannot both hold once the app package owns objects #18202's fix (b8ec127b4,artifactObjectsindefineStack);f89dd33cc,artifactProvidedObjectNames).npm serves
@objectstack/specand@objectstack/lint17.5.0.What remains is hotcrm's. objectstack-ai/hotcrm#1907 (the packaging split) moves its pin to 17.5.0 and lands on its own card. Nothing upstream is left.