Repository navigation
fix(metadata-protocol): an unshipped email-template locale keeps its write before the pair is ensured; the named-base refusal names os migrate packageless-overlays - #30322
Conversation
…stall, before the pair is ensured Claude-Session: https://claude.ai/code/session_01Utp6CYeyrEJGAte4R8B7un Co-authored-by: Claude <noreply@anthropic.com>
…against the pair the ensure door would create, and name the ceremony in the named-base sentence Claude-Session: https://claude.ai/code/session_01Utp6CYeyrEJGAte4R8B7un Co-authored-by: Claude <noreply@anthropic.com>
…ose and the named-base sentence Claude-Session: https://claude.ai/code/session_01Utp6CYeyrEJGAte4R8B7un Co-authored-by: Claude <noreply@anthropic.com>
…gate it asks with the pending pair's closure Claude-Session: https://claude.ai/code/session_01Utp6CYeyrEJGAte4R8B7un Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 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 e6bca36558bfafe06ed6ecaff8bb55367a148e04 && git checkout e6bca36558bfafe06ed6ecaff8bb55367a148e04
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c1240a38ac2c23c8d526e476a20edeabbbefd6f6 b2fc85264885a5f13d3b89fb787e7bf8347a2220 && git checkout -B drift-repro c1240a38ac2c23c8d526e476a20edeabbbefd6f6 && git merge --no-ff b2fc85264885a5f13d3b89fb787e7bf8347a2220
node scripts/docs-audit/affected-docs.mjs --json c1240a38ac2c23c8d526e476a20edeabbbefd6f6
|
|
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb 席位核对 · ACCEPT · 2026-11-05T06:17Z · head
|
Requested by Bill · project thread
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
维护者速读
?package=BASE)同理。基础包已有的语种照旧被拒,并指向.custom配对包。os migrate packageless-overlays,告诉运维这条命令能把老的基础包绑定行挪进配对包(metadata-protocol: the #28905 overlay-write refusal sentence does not nameos migrate packageless-overlays, the ceremony that now moves a base-bound row into its pair #29479)。packages/metadata-protocol/src/protocol-packaged-base-refusal.ts(overlayPairFixOf新增的pendingPairScope、overlayPairFixAdmits的调用、closedOverlayWriteClauses的boundRowServed),packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts(新 describe「a fresh install, the pair not yet ensured」)。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.named-base-overlay-write.test.ts -t "fresh install":三条全绿,两条无包和指名基础包的 fr-FR 写入成功,en-US 对照仍是 403NOT_OVERRIDABLE。Fixes #30168
Fixes #29479
Clause-②: no — the fix restores the write
packagelessCloseSubject's docblock documents as open ("Anemail_templatemember at a locale the base does not ship is refused there (#28777) and stays open"); a pair that is installed was already answered that way, and nothing outside that documented set is admitted.Metadata-protocol: semantic — an author's package-less (or named-base) save of an unshipped email-template locale, refused
403 NOT_OVERRIDABLEon an install whose pair was not yet ensured, now succeeds.Skills-docs: none —
skills/objectstack-platform/SKILL.mdline 128 (PR #29924) already states the package-less write of an unshipped locale is kept ("an email-template row at a locale the base does not ship moves as the pair's own member and keeps that write"); this PR makes the runtime match it.What changes
#30168 — the close read "no such package" as "admitted". Both overlay-write closes (
packagelessCloseSubject, andnamedBaseCloseSubjectfor?package=BASE) close a write only where the fix they name can be taken, and ask the override write gate about the declared body written into the pair (overlayPairFixAdmits). The gate reads the written package's closure from the registry (resolveWritePackageScope). On a fresh install the pairBASE.customis not installed yet, so that read answersundefinedandoverrideWriteVerdictanswersnot_concerned, whichoverlayPairFixAdmitstreats as "not refused". The close then closed a write whose fix is refused once the pair exists (declared_package_ships_no_such_item): the card's double refusal. Every existing pin booted with the pair installed, so none saw it.overlayPairFixOfnow carriespendingPairScopewhen the pair is not installed: the closure the ensure door gives it (one dependency, the base,manifest.dependencies: { [base]: range }), so the base plus what the base reaches, read withresolvePackageClosure, the gate's own walk.overrideWriteVerdict(module function and the class method) takes an optionalwrittenScopeused in place of the registry read for that package. OnlyoverlayPairFixAdmitspasses one; every door's own write passes none, so no door's verdict moves.OverrideWriteVerdictAsktypes the gate as both closes' hosts ask it (NamedBaseCloseHost,PackagelessCloseHost).The first act's measurement (step 1 both ways). On a fresh showcase boot (
pnpm dev -- --fresh, this branch),PUT /api/v1/meta/email_template/showcase_task_done_email:?member=zh-CN:400 VALIDATION_ERROR"The "member" query parameter is not supported by this endpoint … Supported parameters: force, mode, package, surface." The save door is body-addressed (META_ITEM_SAVE_PARAMS), so this spelling never reaches the close.locale: 'zh-CN'in the body: the close reads the body's member (overlayMemberOfBody→zh-CN), the right one. The defect was the gate half above, not the member reading; the docblock is right and the code was wrong.#29479 — the named-base sentence names the ceremony. Since PR #29477,
os migrate packageless-overlaysmoves a row stored bound to the read-only base into the pair with its declaration; the refusal did not say so.closedOverlayWriteClausesgains two clauses, one spelling each:ceremony(the command, now used by both sentences, byte-identical in the package-less one) andboundRowServed, used by the named-base save, publish and rollback sentences: "… is served until moved byos migrate packageless-overlays, overridden by the pair, or deleted naming the base." The previous tail ("served until the pair declares the override, or a delete naming the base removes it") was shortened to fit: with the ceremony added in the long form, the file's 30-character-base / 40-character-name pin measured 525 (save) and 511 (rollback) characters against the REST door's 500 (CLIENT_MESSAGE_MAX). The phrase the dogfood suite pins ("a rollback of its row bound to the read-only base is closed") is unchanged. Docblocks that said the ceremony never moves a base-bound row (NamedBaseWriteOperation,namedBaseOverlayWriteRefusalin both files) are corrected.Tests
packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts:makeProtocol({ pairInstalled: false })boots the base with no pair, and a new top-level describe pins:crm_welcomeatfr-FR(the base ships the canonical member only) is accepted and stored package-less;fr-FRis accepted, as it is with the pair installed;expectClosedSentencenow also asserts theboundRowServedclause on every named-base sentence; the 500-character pin covers the new length.Red first, on origin/main 04f36b6
The CONTROL case was green on main too.
Ablation (one, the behaviour the new pins hold)
node scripts/ablation-replace.mjs --file packages/metadata-protocol/src/protocol-packaged-base-refusal.ts --anchor "item: declaredBody }, fix.pendingPairScope);" --replacement "item: declaredBody } /* ABLATED pendingPairScope */);" -- … vitest run src/protocol.named-base-overlay-write.test.ts -t 'fresh install'onb2fc8526. The suite imports the source (./protocol.js), so nodist/leg applies.Signature notes (for the next PR on these functions)
classifyPackagelessRowis unchanged.overlayPairFixAdmitskeeps its parameters and result; its host'soverrideWriteVerdictis typedOverrideWriteVerdictAsk(one optional parameter,writtenScope), and it passesfix.pendingPairScope.OverlayPairFixgains the optionalpendingPairScope, present only while the pair is not installed.Acceptance notes
200, served at?member=zh-CNas the environment's own member while en-US stays the package's;?package=com.example.showcasefr-FR save200; both en-US controls403 NOT_OVERRIDABLEnaming the pair, the named-base one with the new clause.content/docs/concepts/metadata-lifecycle.mdx's D11 callout says a package-less write onto an item one read-only package ships is refused, without the unshipped-locale exception. This PR does not make it false (the exception was documented in code and skills, not there); left as is.Verification
All on head
b2fc8526(aftergit merge origin/main,1a06be80, then a rebuild:turbo run build --filter='!@objectstack/docs', 119/119).pnpm --filter @objectstack/metadata-protocol test: Test Files 312 passed, 4 skipped; Tests 22382 passed, 26 skipped.pnpm --filter @objectstack/metadata-protocol typecheck: exit 0.packages/objectql:environment-overlay-email-locale-restart.test.ts,protocol-packageless-overlay-migration.test.ts,environment-overlay-five-types.test.ts: 3 files, 106 tests passed (they read the rebuiltdist/).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 60 derived families, each run, allexit 0;--ranreconciliation: "60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED".pnpm lint: exit 0.!, no(narrowing)arm),test:repo, the UI lanes.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01Utp6CYeyrEJGAte4R8B7un