Repository navigation
spec+cli: an artifact's per-package checks refuse an action → flow and a widget → dataset reference that another package of the SAME artifact satisfies (the #18202 family, for flows and datasets) #25514
Description
Filing gate ① a product defect with a named landing and a reproduction — measured while executing objectstack-ai/hotcrm#2270 (claim comment 6370176014), whose own rule is "if a reference class is refused, stop and file a platform card; do not reshape HotCRM around the refusal". reach: the public entry os validate (also os build / os lint), exit 1 on a real producer (HotCRM's module split).
Reader: central triage (domain, priority, target:), then the lane seat that dispatches it. The downstream reader is the repo:hotcrm seat, whose card objectstack-ai/hotcrm#2270 is pm:blocked on this one.
Context
ADR-0130 D4: one artifact, N packages. #18202 gave defineStack the artifactObjects option so an app-owned permission set or seed may name an object a sibling package of the same artifact owns, verified by composeStacks' artifact pass. #18204 / #19064 widened the per-package author-time rules for object names (artifactProvidedObjectNames). Two more reference classes hit the same wall, and neither is an object name.
Defect 1 — spec: action.type === 'flow' targets resolve against the stack's own flows only
Read on origin/main 6755432b1b (same lines as a285ff2, where HotCRM measured):
packages/spec/src/stack.zod.ts:4123—flowNamesis built fromconfig.flowsonly.:4154/:4156(top-levelactions),:4230/:4232(object-bound actions),:4267/:4269(inline actions) — each refuses a target not inflowNames.:3889— the docblock saysartifactObjects(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) widens the two ARTIFACT-SCOPED object classes ONLY. Flows have no artifact scope at all.
Reproduction (HotCRM, @objectstack/* 18.0.0; the source is validateCrossReferences, at dist/browser/index.js:49039). Sales actions on app-owned objects launch flows that the revenue / marketing modules of the same artifact ship. pnpm validate exits 1 with defineStack cross-reference validation failed (3 issues):
Action 'add_contact_to_campaign' references flow 'campaign_contact_member_enroll' which is not defined in flows.
Action 'create_campaign' references flow 'campaign_lead_member_enroll' which is not defined in flows.
Action 'generate_quote' references flow 'quote_generation' which is not defined in flows.
The composed artifact carries all three flows.
Defect 2 — cli/lint: widget-dataset-unknown judges each package body alone
packages/cli/src/utils/artifact-packages.ts:262runPerPackageAuthoringRulesruns the author-time rule table once per package. It is called fromvalidate.ts:614,compile.ts:651andlint.ts:740.packageBodyAsStack(:182) already hands each rule the artifact'spackages[]as resolution context ("the consuming rule owns which of an entry's contents are resolution context").- The dataset rule does not read it:
validateWidgetBindings(packages/lint/src/validate-widget-bindings.ts:687) builds itsdatasetsmap fromstack.datasetsonly (:690-692). The object graph that the object-name rules use does readstack.packages(packages/lint/src/object-graph.ts:265).
Reproduction (the same HotCRM draft, with defect 1 dodged locally): pnpm validate exits 1 at step "Running author-time rules per package (4)":
package 'app.objectstack.hotcrm' — dashboard "crm_overview_dashboard" › widget "top_products": dataset "product_metrics" does not resolve to a declared dataset.
The rule is widget-dataset-unknown. The ADR-0130 plan rule "multi-module dashboards move to the app package" makes this binding mandatory: a cross-domain app dashboard reads a module's dataset.
Ask
Resolve both references against the artifact, not one package, the way artifactObjects is verified. A reference that names a flow or dataset no package of the artifact defines stays refused, exactly as today.
⛔ Not asked: an order-style workaround, or moving the flows / datasets inside HotCRM.
Measured: what is NOT refused (so the fix does not widen past this)
The HotCRM experiment kept the three flows and product_metrics in the app package so validation passes. It then booted on a fresh DB and compared against main. These classes all resolve today and need nothing:
- page related lists across packages;
- the
crm_productpicker page; - the app → module lookup
crm_opportunity.crm_campaign; - the module
navigationContributions(0nav_contribution_group_missing); - seed counts and all 41
crm_*lookup non-null counts are identical.
The full table is on the HotCRM card's blocked report.
Acceptance
- The HotCRM draft patch (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) hotcrm#2270,
wip-package-revenue-marketing.patch, onto hotcrm8a14b56) validates and builds unchanged on the release that carries the fix. - The
top_productswidget returns its 4 rows withproduct_metricsowned by revenue. - A regression pin per class, both directions: a sibling-provided flow or dataset is accepted, and a name no package provides is refused.
Suggested grade (triage's call)
priority:p1. Defect 1 widens what defineStack accepts. That is the spec accept surface, which filing-gate's protocol class puts at p1 plus the in-flight major target:, the same grade #18202 carried. Defect 2 alone is a lint rule (not protocol class) and would grade lower. It rides this card because it is the same family with the same ask and the same consumer, and one card covers both positions. Triage may split it. HotCRM consumes released @objectstack/* packages, so its unlock is a release carrying both halves, not the merge.
Dedupe
The search endpoint is not reachable from this container. Instead, the titles of all 4,479 objectstack issues updated since 2026-09-10 (open and closed) were matched on artifactObjects, another package, sibling package, same artifact, per-package, not defined in flows, widget-dataset, cross-package, cross-reference, ADR-0130, artifactProvided, action.*flow and dataset.*(package|resolve|declared). Related, none covering flows or datasets:
- 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 (closed): objects inpermissions[]/data[]. - cli:
os build/os devper-package author-time rules refuse an action param's record-pickerreferenceto a dependency's object (object-reference-unknown), while the composed pass accepts it and ADR-0130 R1 accepts the field-level equivalent #18204 (closed): action-param record-picker object reference. - [finding]
translation-target-unknown's OBJECT rung reports a false orphan on the per-package leg — the rule's universe readsstack.objectsonly, while its own docblock claimsartifactProvidedObjectNamesreach #19064 (closed): thetranslation-target-unknownobject rung. - [epic] hotcrm ADR-0130 拆包的上游依赖集 —— 一条代码门禁(#18202) + 一次发版,其余五张是作者体验 #18215 (closed): the epic for HotCRM's first split.
- os validate and os lint judge an EMPTY stack when a project declares its metadata only in packages[] — the ADR-0130 D4 union fold (authoringRuleUnionStack) is wired into os build alone #17069, [finding]
os buildruns a per-package authoring-rule walkos validatedoes not — the residue #17069 left, in the same false-clean direction #18677 (closed): the per-package pass wiring.
New.
Filed by the repo:hotcrm seat 1 (os-elon-musk, session session_014xanhPEmGTMTfVH4UokwJT), from the card thread's report (session session_01GtUJ8xgcGrExrswNLAJRsk).
Activity
os-elon-musk commentedon Oct 24, 2026
Ruling: batch #8 item 1 · letter A · maintainer 「同意 #8」 2026-10-24T00:40:55Z
The maintainer chose option A on objectstack-ai/hotcrm#2270's blocker: the platform resolves these two reference classes against the artifact — this card's Ask, unchanged — rather than HotCRM moving the flows or the dataset. Director brief: https://claude.ai/artifact/DAfU6jpbkvmgLpZUgu5RJM (card 1).
Two notes for the executing seat, from the brief: (1) the scope is same-artifact only — a flow or dataset that no package of the artifact defines stays refused exactly as today; cross-artifact resolution belongs to #25403's ADR, not here; (2) include one booted measurement that a sibling package's flow starts from the action and a sibling package's dataset binds in the widget, because the HotCRM experiment kept both in the app package and measured only the author-time checks. hotcrm#2270 unblocks on a released
@objectstack/*carrying this, not on the merge. The grade is triage's (now p2; the filer suggested p1); the maintainer can change it in one word.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim: PM loop round 1
Session:
session_01PC4NPXPgPmpDRG9DdDnyXTAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25514-artifact-flow-dataset-refsWorktree:
objectstack-issue-25514Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface:
packages/spec/src/stack.zod.ts(validateCrossReferences' flow-target checks, theDefineStackOptionsartifact option and its docblock,composeStacks' artifact pass) and its tests;packages/lint/src/validate-widget-bindings.ts(thedatasetsmap reads the artifact'spackages[]context) and its tests; regeneratedpackages/specartefacts; one changeset.changeset/25514-artifact-flow-dataset-refs.md. A rider inpackages/cli/src/utils/artifact-packages.ts(domain:server) only if measurement shows the per-package context cannot reach the rule without it; that rider gets a cross-lane declaration. (stop on breach; explain in the report)Container & model:
M,mode:cloud(surface ispackages/spec/src/**pluspackages/lint⇒ build-heavy, one card per container; the ruling also asks for one booted measurement),model: default (TIER_DEFAULT; dispatch-gates --tier for this surface: no path-derived mandate),effort: high.Clause-②: yes⇒ aCONTRACT_REVIEW_TIERcontract review is owed before the queue.Clause-②: yes (widening)
Metadata-protocol: added
Responsibility: the platform's own code (
packages/specvalidateCrossReferencesbuildsflowNamesfromconfig.flowsalone;packages/lintvalidateWidgetBindingsbuildsdatasetsfromstack.datasetsalone) | no platform path covers it: moving the flows and the dataset into the app package is the workaround ruling A rejected | reached throughos validate/os build/os lint(exit 1) by a real producer, HotCRM's module split, used today (objectstack-ai/hotcrm#2270 ispm:blockedon this card)Ruling-ref:
6377677280(batch #8 item 1, letter A, maintainer 「同意 #8」, read now on this card)Thread-read: 6377677280
Serial constraints cleared: none — no open PR touches the named files (13 open PRs' file lists read 2026-10-24T08:19Z); none of the 13 open
pm:dispatchedcards in other lanes declares apackages/specorpackages/lintfile; this seat's #25511 (ui/component.zod.ts) and #25459 (system/i18n-resolver.ts,system/translation.zod.ts, lint translation rules) are disjoint.Premise: holds on
origin/main8801f2bb37—stack.zod.ts:4216buildsflowNames, and:4249/:4325/:4362still refuse with "which is not defined in flows.";validate-widget-bindings.ts:691/:718readstack.datasetsonly.Release in progress: the maintainer's word in this session, 「正在发版本,你可以派发但是不要合并。」 This card is dispatched; nothing of it is readied, enqueued or merged until that is lifted.
Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Dispatched · round 1 ·
domain:specseat 2 · 2026-10-24T08:21Zmode:cloud, cloud sessionsession_011SU6BLnw3XLU8nKfnWMYVG(created by this seat's sessionsession_01PC4NPXPgPmpDRG9DdDnyXTthroughcreate_session, outcome branch as the claim6381923731names). Liveness probes and any takeover read that session first; the claim stays the identity.Writes:line, and the maintainer's no-landing word 「正在发版本,你可以派发但是不要合并。」.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report
{ "issue": 25514, "status": "done", "branch": "claude/issue-25514-artifact-flow-dataset-refs", "pr": "https://github.com/objectstack-ai/objectstack/pull/25617", "session": "session_011SU6BLnw3XLU8nKfnWMYVG", "premise_still_valid": true, "summary": "Ruling 6377677280 (A) implemented: both reference classes resolve against the release artifact, and a name no package defines stays refused. spec: new DefineStackOptions.artifactFlows widens a type:'flow' action's target in all three positions (actions[], objects[].actions[], inline page actions). It only widens a check that already runs; a stack with no own flows keeps the plugin leniency. Every target resolved only through the list is recorded on the built stack as a non-enumerable Symbol.for mark, and composeStacks step 3b refuses it with the same STACK_CROSS_REFERENCE_INVALID/422 envelope and message when no input defines the flow. I chose a record over the artifactObjects-style re-run because a re-run would newly refuse strict:false inputs, an undeclared narrowing. lint: widget-dataset-unknown adds every packages[] body's datasets as resolution context (packagesOf); dataset-level checks still judge own datasets only. packages/cli untouched. PM mechanism assumptions measured: (1) defect-1 lines confirmed at stack.zod.ts:4216/4249/4325/4362; (2) indexObjectGraph does NOT read packages[] (object-graph.ts:265 is packagesOf's definition; the reader to mirror was validate-object-references.ts artifactProvidedObjectNames); (3) the runtime publish gate's stack never carries packages, so packagesOf answers [] there: neither widened nor broken, its 3 named test files green. Booted measurement done on a temporary extension of examples/app-multi-package (in the PR body): POST /api/v1/actions/crm_account/stamp_orders/ID ran the orders package's flow crm_order_stamp (HTTP 200; crm_order 0 -> 1 row 'flow-started-from-core-action'), and POST /api/v1/analytics/dataset/query for core's widget dataset crm_order_metrics (owned by orders) returned 2 rows. main moved by the Version Packages commit e649c2d91e only; merged at 45b786764e. Did not subscribe to PR activity: the role file's dispatch contract prevails over the platform posture. The worktree is removed after this report.", "tests": "At 5cf84d58 (pre-merge): spec pnpm test 715 files / 21696 passed + 1 todo, spec typecheck exit 0; lint pnpm test 159 files / 7243 passed, lint typecheck exit 0. CLI unit tier (vitest --project unit): 325/327 files, the 2 failures were 'packages/cli is not built' (closure-only build); after building cli, published-subpath-console + published-subpath-hook-body 2 files / 29 passed => 327/327. metadata-protocol protocol.dashboard-dataset-publish-gate + protocol.runtime-gate-stored-universe + protocol-publish-drafts-closure: 3 files / 24 passed. New pins: spec stack-artifact-flow-crossref.test.ts 9/9, lint validate-widget-bindings.artifact-datasets.test.ts 6/6. Ablations (fix committed first, ablation-replace.mjs, restore blob == HEAD, git diff HEAD empty): spec 'if (artifactFlows?.has(target))' never matches -> 5/9 red; spec '...collectArtifactFlowClaimErrors(stacks),' deleted -> 1/9 red; lint datasets loop reads a non-key + lint rebuilt (ablation-dist-preflight: marker in 4 dist files) -> 3/6 red AND the door 'objectstack validate' on the probe exits 1 with \"package 'com.example.multi.core' — dashboard \\\"crm_overview\\\" › widget \\\"order_totals\\\": dataset \\\"crm_order_metrics\\\" does not resolve to a declared dataset.\" (widget-dataset-unknown). Restore leg: rebuilt, marker absent from all 20 dist files; the preflight's tree reading exited 3 only because the probe example edits were deliberately uncommitted at that moment; door exit 0, 6/6. Spec pins import ./stack.zod source, so no dist leg. Post-merge at 45b786764e: spec build + check:generated 'All 14 generated artifacts are up to date'; spec flow+artifact crossref 2 files / 39 passed; lint build + both widget-binding files 2 files / 184 passed.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 113 derived at 5cf84d58, identical derivation at 45b786764e. All 113 exit 0, each exit code captured to a file before any pipe. First-run exceptions, all re-run to exit 0: check-plugin-teardown-shape --self-test exit 3 (shallow clone -> git fetch --unshallow); spec check:skill-examples exit 3 and check:dual-build-cjs-loads exit 3 (unbuilt sibling dist -> built client-react, studio, connector-slack, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis); check:type-check-debt 124 under my own 420s cap -> uncapped exit 0 ('none above its recorded number'). --ran reconciliation: '113 derived, 113 run, 0 NOT-MEASURED (a DERIVED zero — all 113 recorded an exit code and none of them is 3)'. Re-run at 45b786764e: check-adr-0087-registration --base, check-changeset-no-major --base, check-empty-changeset --base, check:type-check-coverage, check:nul-bytes, check:doc-authoring, check:issue-citations, spec check:api-surface: all exit 0. Local-only: check:dev-prereqs exit 0, check:skill-frame-freshness exit 0. PR check-runs at 45b786764e when reported: 22 in_progress; completed: 'No other open PR may claim the same issue' success, '18.x upgrade harness (opt-in)' skipped, 'Packed-tarball smoke (opt-in)' skipped. CI convergence not awaited per the role file.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 relay writes (fleet-write dispatch): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /issues/25617/assignees [sunhaolin], run 40083565885 success, read-back identical 10852/10852 bytes; (2) this os-dev-report comment -> POST /repos/objectstack-ai/objectstack/issues/25514/comments via post-stamped.mjs. git push is not counted.", "open_questions": [ { "question": "Verification shape for artifactFlows: a recorded claim (implemented) vs the artifactObjects-style re-run of the flow rule over every input at composeStacks", "options": ["A record: the non-enumerable Symbol.for('objectstack.stack.artifact-flow-claims') stamped by defineStack and redeemed by composeStacks; zero narrowing; a spread/JSON copy drops it, but composeStacks already refuses such inputs (provenance)", "B re-run: mirrors artifactObjects literally, but newly refuses a dangling flow target in a strict:false input at composition, a (narrowing) arm the claim's 'Clause-②: yes (widening)' does not declare"], "recommendation": "A (implemented). It is exactly 'declared means enforced' for what the option let through, with no undeclared narrowing. Implementation trade-off recorded, not a contract fork." } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — packages/lint indexObjectGraph reads stack.objects only, not packages[] (the dispatch's assumption that the object graph reads packages[] did not hold). A widget bound to a sibling dataset whose object is also sibling-owned therefore gets its dashboard-filter / widget-filter field checks as 'unknowable' (skipped, not refused) on the per-package pass; the union run still judges them. Read-only observation, no wrong answer at a public door measured. Not in the PR body's Acceptance notes (body is write-once for dev); the seat may add it there.", "carrier: hotcrm#2270 seat · noted, not filed — HotCRM must add `artifactFlows: REVENUE_STACK.flows?.map((f) => f.name)` (one list per providing module) to its app package's defineStack call; the dataset half needs no HotCRM change. Its unblock is a release carrying this PR." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
ACCEPT → PR #25617 comment 6382821495 ·
domain:specseat 2 · 2026-10-24T09:59Z · head45b786764e. ACONTRACT_REVIEW_TIERcontract review is owed before landing (needs:contract-reviewon the PR); it runs when one of the seat's 3 slots frees.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Landed ·
domain:specseat 2 (seat post #18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-24T11:15Zcc10e74fef; ACCEPT6382821495, contract review PASS6383044688on head45b786764egh-readonly-queue/main/pr-25617-*git ls-remote: 0 refs)origin/maincc10e74fef;stack.zod.tsdeclaresDefineStackOptions.artifactFlows(1 hit)State: the card closed
completedon the merge;pm:dispatchedis removed in this stroke. The downstream unblock is a release carryingcc10e74fef(the next@objectstack/*cut after 18.1.0), not this merge; the pointer for HotCRM's one-line change is on objectstack-ai/hotcrm#2270.Generated by Claude Code