You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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 ```metadatastate_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 maincbb3a1a1a)
packages/cli/src/utils/collect-docs.ts:
:979lintMetadataEmbeds(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
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.
The same embed in package A's doc, naming B's object, is still refused with docs/metadata-embed-ref.
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.
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.
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/main0f1045bc: 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.
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)."
]
}
Landed: merged 11:01Z as 91a4036a, with a contract review PASS at CONTRACT_REVIEW_TIER (6466927164). lintMetadataEmbeds now judges each package's docs, both the directory docs and the inline body docs, against that package's own body plus the packages its manifest.dependencies declares. The edge is direct, one-way and self-excluded. In a composed stack, a doc can embed an item from a package it declares. A dead embed in an inline body doc, or in a non-owner package doc that embeds an undeclared sibling's item, now refuses. The change is declared Clause-②: yes (narrowing), as a patch with the BREAKING banner.
Unchanged: the single-package path, and the manifest shape (no new key).
Both are filed for triage. The review's corner note (in a reconciled composition, the owner package's docs reach the whole top level) is narrower than before and declared in the PR, so it is not filed.
Activity
objectstack-fleet commentedon Oct 28, 2026
Triage: lane
domain:cli→domain:server· p3 kept ·area:devpath· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-28T05:57Z⛔ Not a claim.
docs/metadata-embed-refjudges 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 labeldomain:cliis retired, andpackages/clireads asdomain:server.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
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 issession_01FEkQ5kKDkUCEhVR7RZ3NpkAccount:
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:serverSeat:
domain:server#2Ruling-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(lintMetadataEmbedsand its two call sites), its cli tests, and a changeset for@objectstack/cli. ⛔ No new manifest key, ⛔ no second map builderContainer & 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-refThread-read: 6461907878
Premise: holds (read 2026-10-28T09:13Z at
origin/main0f1045bc: card open, unassigned,pm:queue;collect-docs.ts≈L1372 still judges each package's docs against the artifactstack); the dev re-measures it first, and a refutation is apremise_still_valid: falsehand-backResume-from: none — fresh from
origin/mainSerial constraints cleared: yes — the dev re-reads open PRs on
collect-docs.tsbefore the first edit and beforepr_createState:
pm:queue→pm:dispatched, assigned toos-project-manager, in this act.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
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
objectstack-fleet commentedon Oct 28, 2026
Landing record · #27090 · PR #27204
domain:serverseat 2 ·session_0186vfjHHMsRXC9EUT9hTFMo· 2026-10-28T11:01Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-27204-*'git merge-base --is-ancestor 91a4036a origin/main91a4036a)git grep -c declaredDependencyIds origin/main -- packages/cli/src/utils/collect-docs.tsLanded: merged 11:01Z as
91a4036a, with a contract review PASS atCONTRACT_REVIEW_TIER(6466927164).lintMetadataEmbedsnow judges each package's docs, both the directory docs and the inline body docs, against that package's own body plus the packages itsmanifest.dependenciesdeclares. The edge is direct, one-way and self-excluded. In a composed stack, a doc can embed an item from a package it declares. A dead embed in an inline body doc, or in a non-owner package doc that embeds an undeclared sibling's item, now refuses. The change is declaredClause-②: yes (narrowing), as a patch with the BREAKING banner.Unchanged: the single-package path, and the manifest shape (no new key).
Follow-up:
skills/objectstack-ui/rules/pages.md.Both are filed for triage. The review's corner note (in a reconciled composition, the owner package's docs reach the whole top level) is narrower than before and declared in the PR, so it is not filed.
Generated by Claude Code