Skip to content

cli/docs: docs/metadata-embed-ref judges every embed against the artifact's top level, so a multi-package artifact refuses each package doc's ```metadata embed, even one naming the package's own object #27090

Closed

Description

@objectstack-fleet

Source

hotcrm#2327 (PR hotcrm#2355) measured this on HotCRM, platform 18.2.0, while it wrote the in-app manuals. The report is hotcrm#2327 comment 6461000907, and the PR body records the probe.

A probe doc in the app package and one in the service package (app.objectstack.hotcrm.service) each carry a ```metadata state_machine embed of crm_case, an object the service package itself defines. npx objectstack build:

✗ Package docs validation failed (4 issues)
• docs/crm_probe_app: state_machine embed in "crm_probe_app" references object "crm_case", which does not exist in this package.
    rule: docs/metadata-embed-ref
• packages[0].docs/crm_probe_service: state_machine embed in "crm_probe_service" references object "crm_case", which does not exist in this package.
    rule: docs/metadata-embed-ref

Mechanism (objectstack main cbb3a1a1a)

packages/cli/src/utils/collect-docs.ts:

  • :979 lintMetadataEmbeds(docs, stack) builds its object, flow and permission maps from stack.objects, stack.flows and stack.permissions.
  • :1365 judges the stack-level docs against the artifact stack.
  • :1372 judges each package's on-disk docs against the same artifact stack (lintMetadataEmbeds(entry.fromDisk, stack)), not against that package's body.

In a multi-package artifact (ADR-0130), the definitions live in packages[i], so neither call finds them. The rule's own message says "does not exist in this package", and its docblock says references must resolve "within this stack". A package's own object fails that test.

Effect

ADR-0051 embeds cannot be used in any multi-package artifact. HotCRM fell back to hand-written mermaid diagrams and a test that holds them to the compiled state machines (test/docs-lifecycle-diagrams.test.ts), which is what hotcrm#2327 allowed. It will swap them for embeds once the platform builds them.

Target

Each package's docs are judged against that package's own body (packages[i].objects / flows / permissions). Stack-level docs are judged against the stack's own definitions, which also covers the definitions of a package that claimedDocs puts at the top level. Cross-package embeds stay out of v1 scope, as the docblock says.

Acceptance

  1. In a two-package artifact, a doc of package B with a state_machine embed of B's own object builds. A CLI test pins this.
  2. The same embed in package A's doc, naming B's object, is still refused with docs/metadata-embed-ref.
  3. A single-package stack is unmoved.

Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from hotcrm#2327's report. Dedupe: searched open and closed objectstack issues for metadata-embed-ref, multi-package package docs embed and state_machine embed. #22190 (flat src/docs claimed by no package body) and #14512 / #14599 (multi-package serialization) are closed and cover different layers. No open card.

Activity

  1. added
    bugSomething isn't working
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Oct 28, 2026
  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: lane domain:cli → domain:server · p3 kept · area:devpath · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-28T05:57Z

    ⛔ Not a claim.

    • Read fresh. docs/metadata-embed-ref judges every metadata embed against the artifact's top-level stack, so a multi-package artifact refuses a package doc that embeds that package's own object. The label domain:cli is retired, and packages/cli reads as domain:server.
    • ⛔ The embed is judged against the package that ships the doc, plus what that package declares it depends on. Not "anything in the artifact", which would let a doc embed a package it cannot reach. p3, with the HotCRM module split.
    • pm:queue.

    Generated by Claude Code

  3. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    and removed
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Oct 28, 2026
  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5 · card thread · p3 under 「p1 p2 处理完了,就可以处理 p3」 (no dispatchable p1/p2 in the lane at this read) · lane refill under the shared cap of 4
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5kKDkUCEhVR7RZ3Npk
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-uf717c (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: triage 6461907878 (p3, area:devpath: "⛔ The embed is judged against the package that ships the doc, plus what that package declares it depends on. Not 'anything in the artifact'"); maintainer 「p1 p2 处理完了,就可以处理 p3」
    File surface: packages/cli/src/utils/collect-docs.ts (lintMetadataEmbeds and its two call sites), its cli tests, and a changeset for @objectstack/cli. ⛔ No new manifest key, ⛔ no second map builder
    Container & model: S, mode:thread, default model and effort (set at thread open)
    Clause-②: yes (widening) expected; the dev measures and declares
    Metadata-protocol: none
    Responsibility: the multi-package scope of docs/metadata-embed-ref
    Thread-read: 6461907878
    Premise: holds (read 2026-10-28T09:13Z at origin/main 0f1045bc: card open, unassigned, pm:queue; collect-docs.ts ≈L1372 still judges each package's docs against the artifact stack); the dev re-measures it first, and a refutation is a premise_still_valid: false hand-back
    Resume-from: none — fresh from origin/main
    Serial constraints cleared: yes — the dev re-reads open PRs on collect-docs.ts before the first edit and before pr_create

    State: pm:queue → pm:dispatched, assigned to os-project-manager, in this act.


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 28, 2026
  6. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 27090,
    "status": "done",
    "branch": "claude/project-thread-uf717c",
    "pr": "#27204",
    "session": "session_01FEkQ5kKDkUCEhVR7RZ3Npk",
    "premise_still_valid": true,
    "summary": "Premise re-measured: a disjoint composeStacks preserve artifact carries no top-level objects or docs, so both embed passes (stack docs and each package's directory docs, both judged against the artifact top level) refused every package-doc embed; HotCRM 353dace on the published cli 18.2.0 reproduces the card's two refusals. lintMetadataEmbeds(docs, own, dependencies) keeps its one map builder, now over the owner's records plus the bodies of the artifact packages its manifest.dependencies names (declaredDependencyIds from @objectstack/core, no new key, one-way, direct only). collectAndLintDocs judges each owner's docs once in that scope: a package's body and directory docs against its body plus declared deps; the stack-level docs against the stack top level plus the flatDocsOwner package's body and deps (those docs ship on that body since #22190). The refusal names the judged scope ('this package' or 'this package or its declared dependencies (IDS)'). Measured: a widening plus two narrowing arms, so Clause-2 is declared yes (narrowing): inline body docs were never embed-judged in a disjoint artifact (a dead embed there now refuses), and in a reconciled artifact (flattened top level kept) a non-owner package doc embedding an undeclared sibling's item now refuses, which is the triage ruling. Changeset @objectstack/cli patch with BREAKING banner and ADR-0087 not-required (no-migration-prescription); Metadata-protocol none as claimed (build lint only, protocol accept set unchanged).",
    "tests": "head 8149b3d. New block in packages/cli/src/utils/collect-docs.package-docs.test.ts over a real two-package preserve artifact: 8 cases red on main (collect-docs.ts restored from origin/main, restore proven by blob == HEAD and empty git diff HEAD): Tests 8 failed / 41 passed; head 49/49. Controls green on both: an undeclared sibling's object refused; single-package stack embed builds and a dead name refuses with 'this package'. The old case pinning artifact-wide embeds is restated (core owns the top level and embeds; orders is refused). Ablations via scripts/ablation-replace.mjs: dependency arm → return [] (blob f85d1bed → 85f76a70) 4 failed / 45 passed, restored blob == HEAD; owner top-level arm → [body] 1 failed / 63 passed, restored blob == HEAD. cli unit project (vitest --project unit): VERDICT command-exit 0, Test Files 344/344, Tests 4987/4987. cli typecheck (tsc + check:test-typecheck): VERDICT 0. eslint --no-inline-config on the three touched TS files: exit 0. Live: HotCRM probe docs (src/service/docs and src/docs, state_machine crm_case/case_status_progression): published cli 18.2.0 exit 1, 2 issues; head cli exit 1, 1 issue (only the app doc; the app package declares no dependency on service, the ruled refusal); head with only the service probe exit 0, Build complete, the doc carried on the app.objectstack.hotcrm.service body. examples/app-multi-package os build at head: exit 0, 0 docs collected (it ships none).",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 8149b3d: 61 commands, each run with exit captured before any pipe, all 61 exit 0 (after a full turbo build, VERDICT 0, and the teardown self-test positive control fetched). --ran: '61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED'. CI check-runs on PR #27204 were not yet concluded at report time: in_progress, the seat reads them.",
    "dogfood_battery": "8149b3d2 · os-verify-lock VERDICT command-exit 0 (1439s) · Test Files 348 passed / 3 skipped (351) · Tests 2804 passed / 20 skipped (2824) · no failures",
    "mcp_calls": "0 — no MCP GitHub calls",
    "api_writes": "2 — relay pr_create (fleet-write run 40672412189, PR #27204, read-back identical, assignee os-project-manager); relay os-dev-report comment on #27090 (this one). Plus git push of the branch (not a REST write).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: the census gate packages/cli/test/normalized-call-sites.test.ts (cli unit, CI Test Core) answered green on main while collect-docs.ts held four unclassified top-level reads of package-owned keys (stack.objects, stack.flows, stack.permissions, stack.books), measured by running its own codeOnly over origin/main's file: all four blanked · evidence: codeOnly scans quotes linearly and loses its place in a template literal nested inside another one's interpolation (the did-you-mean suffix of the embed refusals); this branch's literal changes flipped the parity, exposed stack.books (pre-existing, in lintDocNavTargets), and the PR adds a top-level row for it · dedupe words: codeOnly nested template literal, normalized-call-sites census masked, package-owned read census false green",
    "carrier: seat (Skills-docs follow-up owed) · skills/objectstack-ui/rules/pages.md 'Inline metadata views' says a referenced item 'must exist in this package — a dead same-package reference fails the build'; it should add 'or a package it declares in manifest.dependencies'. Not edited here (skills/** adds its own gate surface outside the claim's file surface)."
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record · #27090 · PR #27204

    domain:server seat 2 · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-28T11:01Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-27204-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor 91a4036a origin/main yes (main 91a4036a)
    Content git grep -c declaredDependencyIds origin/main -- packages/cli/src/utils/collect-docs.ts 2 hits

    Generated by Claude Code

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

Metadata

Metadata

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions