Skip to content

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

Closed

Description

@objectstack-fleet

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):

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:262 runPerPackageAuthoringRules runs the author-time rule table once per package. It is called from validate.ts:614, compile.ts:651 and lint.ts:740.
  • packageBodyAsStack (:182) already hands each rule the artifact's packages[] 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 its datasets map from stack.datasets only (:690-692). The object graph that the object-name rules use does read stack.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_product picker page;
  • the app → module lookup crm_opportunity.crm_campaign;
  • the module navigationContributions (0 nav_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

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:

New.

Filed by the repo:hotcrm seat 1 (os-elon-musk, session session_014xanhPEmGTMTfVH4UokwJT), from the card thread's report (session session_01GtUJ8xgcGrExrswNLAJRsk).

Activity

  1. os-elon-musk commented on Oct 24, 2026

    @os-elon-musk
    Collaborator

    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

  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01PC4NPXPgPmpDRG9DdDnyXT
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-25514-artifact-flow-dataset-refs
    Worktree: objectstack-issue-25514
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface: packages/spec/src/stack.zod.ts (validateCrossReferences' flow-target checks, the DefineStackOptions artifact option and its docblock, composeStacks' artifact pass) and its tests; packages/lint/src/validate-widget-bindings.ts (the datasets map reads the artifact's packages[] context) and its tests; regenerated packages/spec artefacts; one changeset .changeset/25514-artifact-flow-dataset-refs.md. A rider in packages/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 is packages/spec/src/** plus packages/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 ⇒ a CONTRACT_REVIEW_TIER contract review is owed before the queue.
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: the platform's own code (packages/spec validateCrossReferences builds flowNames from config.flows alone; packages/lint validateWidgetBindings builds datasets from stack.datasets alone) | no platform path covers it: moving the flows and the dataset into the app package is the workaround ruling A rejected | reached through os validate / os build / os lint (exit 1) by a real producer, HotCRM's module split, used today (objectstack-ai/hotcrm#2270 is pm:blocked on 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:dispatched cards in other lanes declares a packages/spec or packages/lint file; 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/main 8801f2bb37 — stack.zod.ts:4216 builds flowNames, and :4249 / :4325 / :4362 still refuse with "which is not defined in flows."; validate-widget-bindings.ts:691 / :718 read stack.datasets only.

    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

  3. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatched · round 1 · domain:spec seat 2 · 2026-10-24T08:21Z

    • Back end: mode:cloud, cloud session session_011SU6BLnw3XLU8nKfnWMYVG (created by this seat's session session_01PC4NPXPgPmpDRG9DdDnyXT through create_session, outcome branch as the claim 6381923731 names). Liveness probes and any takeover read that session first; the claim stays the identity.
    • Order given: the dispatch prompt carries the claim's file surface, the declarations, the consumer-package test list, the Writes: line, and the maintainer's no-landing word 「正在发版本,你可以派发但是不要合并。」.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25617 comment 6382821495 · domain:spec seat 2 · 2026-10-24T09:59Z · head 45b786764e. A CONTRACT_REVIEW_TIER contract review is owed before landing (needs:contract-review on the PR); it runs when one of the seat's 3 slots frees.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:spec seat 2 (seat post #18549) · sunhaolin · session session_01PC4NPXPgPmpDRG9DdDnyXT · 2026-10-24T11:15Z

    reading value
    PR #25617 MERGED through the queue at 2026-10-24T11:14:53Z; merge commit cc10e74fef; ACCEPT 6382821495, contract review PASS 6383044688 on head 45b786764e
    queue branch gh-readonly-queue/main/pr-25617-* gone (git ls-remote: 0 refs)
    origin/main carries cc10e74fef; stack.zod.ts declares DefineStackOptions.artifactFlows (1 hit)

    State: the card closed completed on the merge; pm:dispatched is removed in this stroke. The downstream unblock is a release carrying cc10e74fef (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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions