Repository navigation
fix(spec): composeStacks says why a top-level connector beside a package body's same-named entry is ambiguous - #28026
Conversation
…age body is ambiguous The composed external.connector check refused a top-level connector beside a package body's same-named entry for the reason that holds between two bodies. The boot drops the top-level twin and a dev reload replaces the body's entry with it; the finding, the slot doc and the docblock now say so, a top-level-only disagreement gets its own sentence, and a preserve input that carries packages beside its own top level is pinned in both orders. No verdict moves. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2cLiZUHvCtV2YGRWAd
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d7ab1fa78f101a13ca819268dbc2f55aa6eef513 && git checkout d7ab1fa78f101a13ca819268dbc2f55aa6eef513
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e1dde1a5c15566bcc24d6c641b53d8282fcf655b 541d7f5b3dec8648875f63347de6319b39c767eb && git checkout -B drift-repro e1dde1a5c15566bcc24d6c641b53d8282fcf655b && git merge --no-ff 541d7f5b3dec8648875f63347de6319b39c767eb
node scripts/docs-audit/affected-docs.mjs --json e1dde1a5c15566bcc24d6c641b53d8282fcf655b |
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV The seat's at-tier review call is refused by the model quota (last probe 2026-10-30T07:46Z: HTTP 429 rate_limit), and per contract-review.md the tier is not lowered. One same-shape
|
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Full review at ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Findings. No must-fix. Should-fix 1: the beside-body sentence's reload half ("replaces it with the top-level one, so the datasource could boot or reload with no credential") overstates under Implemented-by: VERDICT: PASS Generated by Claude Code |
…top-level-only sentence A dev reload replaces the booted entries of a name with the artifact's top-level entries of it and lends the first, so the beside-a-body sentence now says that and no longer claims a reload without a credential in every order. The top-level-only sentence states its mechanism the same way and gets a preserve case in both orders; the mixed-input CONTROL pins the kept top-level order. No verdict moves. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2cLiZUHvCtV2YGRWAd
|
epic:#27261#1 · marchtian · session_017ihL2cLiZUHvCtV2YGRWAd Record 6519281283 (PASS on
|
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Delta review at What moved since the previous head, read locally (both commits present): two commits. The PR's own commit 1580237 ("say which top-level entry a dev reload lends, and pin the top-level-only sentence": 3 files, +53/-21, all three inside the claim's file surface), then the merge commit that is this head, bringing ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Findings. No must-fix. No should-fix: the two should-fixes of record 6519281283 are discharged as that record asked, each sentence is true on every shape that renders it, and no verdict of Implemented-by: VERDICT: PASS Generated by Claude Code |
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV 维护者速读
席位意见:ACCEPT。契约复核 PASS 已在案:6519281283 在 席位核对
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Requested by 田騏 · project thread
Full review owed at the reviewer tier. Developed on the default tier while the reviewer tier's quota is out. This PR touches the published spec (
packages/spec/src/**), so it lands only after a full review at the reviewer tier and aCONTRACT_REVIEW_TIERcontract review record PASS on the current head.Fixes #28006
Clause-②: no
Metadata-protocol: none
维护者速读
slot文档和providedCollectionEntries的说明也写清了。另外改了一个测试名,并补了preserve下混合嵌套输入的测试。packages/spec/src/stack.zod.ts中collectComposedDatasourceConnectorErrors的歧义分支,以及ProvidedCollectionEntry.inBody的文档。pnpm --filter @objectstack/spec exec vitest run src/stack-datasource-connector.test.ts,预期 30 个测试全过。What changes
packages/spec/src/stack.zod.ts:ProvidedCollectionEntrygainsinBody: whether the entry boots as a package body's own item. That covers an entry of apackages[]body, and, underpreserve, the top level of an input thatpreservePackageEntriesfolds into a body of its own id. Its doc states the mechanism for every other top-level entry beside package bodies. The residual rule (unclaimedTopLevel) does not register such an entry when a body declares its name. When no body declares the name, the rule registers it under the composed artifact'smanifest.id(the last input's). A dev reload replaces the booted entries of the name with the artifact's top-level list (mergeDeclaredConnectorSources). So itsslot('artifact', or a mixedpreserveinput's ownpackage:${id}) is a stand-in. It stays a stand-in because keeping it apart from the bodies is what refuses the boot-or-reload split as ambiguous.providedCollectionEntries' docblock andcollectComposedDatasourceConnectorErrors' docblock say the same.packages/spec/src/stack-datasource-connector.test.ts:preservereading of an input that carriespackagesbeside its own top-levelconnectors. With a descriptor at its top level and another package's lending instance, it is refused as ambiguous, with the beside-a-body sentence, in both input orders. With two lending instances it composes in both orders. The composed artifact keeps that top level beside the inner and plain bodies only, under the last input'smanifest.id, and the whole flattenedconnectorslist in input order (pinned both ways, since a dev reload lends from the first). Two such inputs whose top-level entries disagree, beside bodies silent on the name, are refused with the top-level-only sentence in both orders..changeset/28006-composed-connector-ambiguity-reason.md:@objectstack/specpatch(the message text ships), both declaration lines, and an ADR-0087not-required (no-migration-prescription)marker.Why
Should-fix 1 of contract record 6516406275 on PR #27939: the refusal was right for a reason the message, the slot doc and the test name did not give.
Verification
All on head
541d7f5b3d(should-fixes 1 and 2 of contract record 6519281283 in 1580237, then a merge oforigin/main).origin/main'sstack.zod.ts. The 25 that do not assert the new wording passed. The 5 that do failed only on that wording: each first asserted the envelope (STACK_CROSS_REFERENCE_INVALID, 422) and exactly one finding for the datasource, and those assertions passed. So every composition this file covers is refused or accepted exactly as before.preserverefusal go red, and the mixed-first order is accepted. The 26 other cases stay green. Restored, 28/28 pass (measured on55b0c85641, before the two cases should-fix 2 added).pnpm --filter @objectstack/spec build,testandtypecheckunder the verify lock: build exit 0; 756 test files passed, 22505 tests passed (1 todo); typecheck passed (check:test-typecheck: OK).node scripts/pm/dispatch-gates.mjs --commands, derived after mergingorigin/main, named 80 families (re-derived after the second merge oforigin/main). All were run, with exit codes recorded before any pipe, and every one exited 0.dispatch-gates --ran: "80 derived famil(ies) accounted for — 80 run, 0 NOT-MEASURED (a DERIVED zero)". The tree was clean after the gate run, so no generated reference moved. The full gate battery is CI's. Dogfood was not run:Clause-②: nodoes not owe it, and no verdict moves.Acceptance notes
manifest.idslot, where the residual rule registers it, would move a verdict: with the plain input last, a descriptor at the mixed input's top level would be judged last-wins against the plain body's instance and accepted. The card forbids that, so the doc states the mechanism instead.objectstack-issue-28006on the claimed branch, as the Claim names.Human Minimum
packages/specholds no runtime logic. (Message wording of a build-time collector and one module-private field; no schema changed.)pnpm lintis CI's.main, one pull request.packages/spectouched: no generated artifact changes (only JSDoc and a refusal's text; the gate run left the tree clean).content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_017ihL2cLiZUHvCtV2YGRWAd