Skip to content

fix(spec): the packageless-overlay-write-closed upgrade entry names both closed overlay write addresses - #29535

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-y5cujk
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-y5cujk

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #29393

Clause-②: no (migration entry prose; no accept set moves)
Metadata-protocol: none

维护者速读

  • 改了什么:只改文字。未发布的升级条目 packageless-overlay-write-closed(ADR-0087 语义迁移)的 surface 与 reason 现在同时点名两个被关闭的覆盖写入地址:不带包的写入,和点名只读基础包本身(?package=基础包)的保存、发布、回滚,并点名这两种写入各自存下的行;replacement 补上绑定在基础包上的行的迁移与处置(与 CLI 打印的一致)。
  • 为什么改:metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905 在同一版本里关闭了第二个地址,但这条升级条目只写了第一个;升级指南由它生成,运维读不到第二个地址。来源:合同复核记录 6577747588 的 S3。
  • 风险与回滚:无运行时改动,只影响生成的升级指南与 spec-changes 文字。回滚即 revert 本 PR。
  • 先看:packages/spec/src/migrations/entries/semantic/18.packageless-overlay-write-closed.ts 的 surface(两个编号地址)、reason(新增两句)与 replacement(绑定行的 foreign_declaration 处置)。
  • 两分钟验证:pnpm --filter @objectstack/spec check:upgrade-guide,预期 exit 0;用 --out 生成的指南里该条目出现 naming the read-only package itself 与 publish-drafts。
  • 需要你做的:无,席位落地。

What changes

Text only, one entry. packages/spec/src/migrations/entries/semantic/18.packageless-overlay-write-closed.ts (unreleased ADR-0087 semantic entry, shipping in the same release as the two closes):

  • surface now names both closed addresses onto a view, dashboard, report, translation or email template an installed read-only package ships: (1) the write naming no package, worded as before; (2) the write naming the read-only package itself: a save in draft or publish mode or a per-item publish with ?package=BASE, POST /api/v1/packages/BASE/publish-drafts holding such a draft, and a rollback of a row bound to the package, where the pair can be ensured, the base records a version and the override write gate admits the declared body (a delete naming the package keeps the write). The stored-rows clause now covers both: package-less rows, and rows bound to the read-only package itself (the rows os migrate packageless-overlays also moves since migrate: rows bound to a read-only base package stored before the #28905 close are invisible to os migrate packageless-overlays, so their only exit drops the customization #29392).
  • reason gains two sentences: what each address stored (a row of no package, which no export carries; a row bound to the package, declaring nothing, served in place of its own document), and the named close (refused 403 NOT_OVERRIDABLE naming the pair and the overrides declaration where the pair can take the declared override; a delete naming the package stays open, and so does every write the pair could not take, an email template locale the base does not ship among them). Everything else in reason is unchanged.
  • replacement (claim amendment 6584492790): the movable rows --apply moves are package-less OR bound to the read-only package itself (the preview's "bound to", --json boundToBase, which the ceremony lists and moves since migrate: rows bound to a read-only base package stored before the #28905 close are invisible to os migrate packageless-overlays, so their only exit drops the customization #29392). An unmoved package-less row keeps its package-less write; an unmoved bound row keeps its delete naming the package. A bound foreign_declaration row gets the remedy the cli prints (reasonOf, the boundToBase arm, in packages/cli/src/commands/migrate/packageless-overlays.ts): delete it naming the package, then save its body into the pair, declaring overrides, since a save naming the package is closed; the package-less remedy is unchanged. The failed-move tail: a bound row keeps its delete naming the package, and the save naming it where the pair could not take the override (for those two causes namedBaseCloseSubject answers null). The TO both closes share (write into BASE.custom, declaring overrides) is unchanged. acceptanceCriteria is untouched.
  • The header comment above the entry names both closed addresses and both kinds of stored row.
  • .changeset/29393-overlay-write-entry-both-addresses.md: @objectstack/spec patch, both declaration lines, one sentence each for surface/reason and replacement.

Measured against the code on origin/main (399df5b): namedBaseOverlayWriteRefusal / namedBaseCloseSubject in packages/metadata-protocol/src/protocol-packaged-base-refusal.ts, and the #28905 changeset's Now refused / Still open lists, which the new wording follows clause for clause.

The card's conditional line (commit revert path)

The card says to add a line if the S1 card decides the commit revert path stays open. The S1 card is #29390; its seat ruling (comment 6579511992) is fork A: the revert's restore limb is closed with the same sentence, so the path does not stay open. No such line is added. #29390 has no PR yet; when it lands, its own ADR-0087 disposition decides whether this entry's address (2) also names the commit revert and package rollback.

Why

Contract review 6577747588 of PR #29270, should-fix S3 (the dev's open question 1, answered A): the upgrade guide is generated from this entry, and a reader should meet both closed addresses in one entry. Triage 6582524180 set p1: it lands before the 18.3.0 cut.

Tests

No test pins this entry's text (git grep for the entry id hits only the entry and two changesets). The checks that read it, on b99d86a:

  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/migrations scripts/upgrade-guide-docs.test.ts scripts/upgrade-guide-surface-span.test.ts under the lock, after pnpm --filter @objectstack/spec build (VERDICT command-exit 0): Test Files 7 passed (7), Tests 226 passed (226), VERDICT command-exit 0 (also 226/226 on 6b1ea51 and ecf1fd4).
  • check:upgrade-guide exit 0 (protocol 18.0.0, 2 hop sections); check:spec-changes exit 0.
  • Generated with tsx scripts/build-upgrade-guide.ts --out to a scratch file: the entry renders as one surface code span holding both numbered addresses; naming the read-only package itself 2 hits, publish-drafts 5 hits, boundToBase 1 hit, ship the name): delete it naming no package 1 hit, and the save naming it where the pair could not take the override 1 hit.

Acceptance notes

  • No checked-in artifact regenerates from this entry: the upgrade guide and spec-changes.json are generated in memory at PR time and at publish (build-upgrade-guide.ts header), and the docs pages are gitignored.

Verification

Derived gates on ecf1fd4 (b99d86a is a prose-only follow-up in the same file)
  • Prerequisite builds under the lock: pnpm --filter @objectstack/spec build (VERDICT command-exit 0); turbo run build --concurrency=2 over the objectql, lint, formula, client and client-react closures (VERDICT command-exit 0, 38/38 tasks).
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack re-derived on ecf1fd4: 76 families (2 paths vs merge base 399df5b, +50/-19). Each run once, stdin from /dev/null, exit captured before any pipe: 75 exit 0 (the teardown self-test after its pinned fixture 621a4876 was fetched on the first round).
  • pnpm check:dual-build-cjs-loads: exit 3 (PREREQUISITE NOT MET: 68 workspace packages unbuilt). NOT MEASURED locally; CI runs it on a full build.
  • dispatch-gates --ran: 76 derived, 75 run, 1 NOT-MEASURED (dual-build, from its recorded exit 3), 0 UNRUN.
  • check-changeset-no-major.mjs --base origin/main --event with this body: exit 0; LEVEL AXIS reads Clause-②: no + Metadata-protocol: none, patch.
  • On b99d86a (the review's three text fixes, same file): spec build, the migrations and upgrade-guide tests (226/226), check:upgrade-guide and check:spec-changes exit 0. The derived set is a property of the changed paths, which did not change.
  • check:dev-prereqs (local-only family): exit 1, the workspace is not fully built (68 of 106 packages); a reading of this container, not of the diff.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic (prose only).
  • 2. No name added.
  • 3. No formatter ran; pnpm lint is CI's run.
  • 4. One issue, one branch off main, one pull request.
  • 5. Patch changeset with both declaration lines, here and in the changeset.
  • 6. packages/spec touched: no checked-in artifact regenerates from this entry (the guide and spec-changes are generated in memory at PR time).
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. English.

Generated by Claude Code

…ed overlay write addresses

The unreleased ADR-0087 semantic entry's surface and reason named only the
write naming no package. The same release also closes the save, publish and
rollback naming the read-only package itself onto its five overlay types,
onto the TO the entry's replacement already prescribes. One entry now names
both addresses and the rows each stored. Text only.

Claude-Session: https://claude.ai/code/session_01JQwxbWytk9Ec5dXr38QrwZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s).

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/environment-routing.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/automation/connectors.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/deployment/app-dependencies.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/deployment/marketplace-handbook.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/frontend/plugins/plugin-chatbot.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/getting-started/examples.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/kernel/contracts/metadata-service.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/permissions/permission-sets.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/protocol/kernel/error-handling.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/protocol/kernel/http-protocol.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/ui/apps.mdx (via /api/v1/packages (route, a path literal in surface))

⛔ 7 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/releases/v17/17-4.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/releases/v17/17-5.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/releases/v17/17-7.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/releases/v18/18-0.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/releases/v18/18-1.mdx (via /api/v1/packages (route, a path literal in surface))
  • content/docs/releases/v18/18-2.mdx (via /api/v1/packages (route, a path literal in surface))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: /api/v1/meta (route, 47 pages)
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8e505db8178cedbed174049b211cc409b22adaea → packageMentionDocs.

Which tree this was computed on

This run read content/docs from da402338d742a106576c146871270878dd8fe707 — the merge of head b99d86a144ed9c23d9170c5802a2f27ac480dd90 into base 8e505db8178cedbed174049b211cc409b22adaea, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin da402338d742a106576c146871270878dd8fe707 && git checkout da402338d742a106576c146871270878dd8fe707
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8e505db8178cedbed174049b211cc409b22adaea b99d86a144ed9c23d9170c5802a2f27ac480dd90 && git checkout -B drift-repro 8e505db8178cedbed174049b211cc409b22adaea && git merge --no-ff b99d86a144ed9c23d9170c5802a2f27ac480dd90

node scripts/docs-audit/affected-docs.mjs --json 8e505db8178cedbed174049b211cc409b22adaea

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8e505db8178cedbed174049b211cc409b22adaea → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ws bound to the read-only package

Since the ceremony also moves rows bound to the read-only package itself,
the entry's replacement names them, and gives a bound foreign_declaration
row the remedy the cli prints (delete it naming the package, then save its
body into the pair, declared), since a save naming the package is closed.
The package-less remedies are unchanged. Text only.

Claude-Session: https://claude.ai/code/session_01JQwxbWytk9Ec5dXr38QrwZ
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ecf1fd4a39fb1e0b188e276bfa3ba14c62eea2e7
Local-runs: none
Scope: packages/spec/src/migrations/entries/semantic/18.packageless-overlay-write-closed.ts + packages/metadata-protocol/src/protocol-packaged-base-refusal.ts (namedBaseOverlayWriteRefusal, namedBaseCloseSubject, closedOverlayWriteClauses, NamedBaseWriteOperation), packages/metadata-protocol/src/protocol.ts (the save, per-item publish, publishPackageDrafts / promoteDraftForPublish, rollback and delete doors), packages/metadata-protocol/src/protocol-packageless-overlay-migration.ts (the census and move, #29392), packages/cli/src/commands/migrate/packageless-overlays.ts (reasonOf, jsonRow, the preview listing), .changeset/28905-named-base-overlay-write-closed.md (the #28905 Now refused / Still open lists), card #29390's live state (ruling 6579511992). Read on origin/main 390bd2ae; the PR's own changeset is prose outside hits and is the lane seat's ACCEPT at TIER_DEFAULT.

Inputs: card #29393 body and all five comments (triage 6582524180, claim 6583771832, reports 6584466731 and 6585010191, claim amendment 6584492790); PR #29535 body, file list (2 files, +50/-19) and the net diff against main at the head; the 63 check-runs on the head. The PR body was written at 6b1ea51 and is stale on the replacement (it says untouched; the tree edits it under the amendment) — judged on the tree, as the brief says.

Check-runs on ecf1fd4a: 63 total — 53 success, 9 skipped (opt-in and conditional jobs: 18.x upgrade harness, packed-tarball smoke, Build Docs, Lint UI, UI Published Artifacts, Temporal Conformance, Dogfood Verify CLI, the dogfood matrix shell, the schedule gate), 1 in_progress (Test Core 6/8) at review time. Every concluded gate is green, including Check Changeset, Governed Surface Queue Guard, PR-body guards, Spec property liveness, the Lint and Type Check families and Test Core 1-5, 7, 8. Judged on what has concluded; the one running shard is named, not assumed.

① Derived judgments

② Semver level

.changeset/29393-overlay-write-entry-both-addresses.md: @objectstack/spec: patch, Clause-②: no (migration entry prose; no accept set moves), Metadata-protocol: none. Matches the diff: prose of an exported object in one package, no narrowing arm, no ! title, no BREAKING banner. The parenthetical after no is ordinary reasoning, not an arm (check-changeset-no-major.mjs's own control at its line 3743 reads exactly this shape as a plain no); Check Changeset concluded success on the head. No ADR-0087 marker is owed: the marker is required of a breaking change, and this is neither breaking nor a registration (the registration marker lives on 28576-packageless-overlay-write-closed.md, and #28905's changeset already records not-required (already-registered packageless-overlay-write-closed) naming this very widening as the packages/spec edit it did not make). The PR body carries the same two declaration lines. Clause-②: no — right.

③ Boundary flags

  • Dev open question (report 6584466731): the replacement did not fit rows bound to the base (the foreign_declaration remedy, and "every row ... keeps its package-less write"). Answered A by claim amendment 6584492790 (seat widened the file surface to the replacement text, same PR); implemented in ecf1fd4a; report 6585010191 lists no open question. Closed.
  • Claim surface: two files, both inside the amended claim. packages/spec/src/api/protocol.zod.ts (frozen by server seat 5 until 06:00Z) is untouched. Held.
  • Dev deviations: the model-free commit trailer pair per AGENTS.md (a repo rule, not a review matter); check:dual-build-cjs-loads NOT MEASURED locally — covered by CI (Build Core, Type Check · workspace, Lint families all success). Closed.
  • body_amendments: the dev names four stale places in the PR body (the replacement bullet, the acceptance-notes bullet now done, the test head, the gate counts) and asks the seat to write them. Not a tree defect; the seat owes the body edit before merge so the record of what landed is not contradicted by the PR's own account. Escalated to the seat, no verdict weight.
  • Out-of-scope carrier (metadata-protocol: a commit revert or package-commit rollback can restore a row bound to a read-only base package that the #28905 close now refuses to write #29390's revert close and the entry's address (2)): noted on the card, correctly left to that card.

④ Design judgment

18.packageless-overlay-write-closed.ts, judged against the code it cites on main and the scenario it is accepted against (an operator reading the generated 18.x upgrade guide after both closes shipped).

surface — address (2) against namedBaseCloseSubject and the doors. Holds, clause for clause. The doors that ask the named-base close on main: the save door (protocol.ts ~15721, request.source === undefined, draft or active — "a save in draft or publish mode"); promoteDraftForPublish (~18613, judged on promoteInto, before repo.promoteDraft), reached from publishMetaItemUnaudited (18166, the per-item POST /api/v1/meta/TYPE/NAME/publish?package=BASE) and from publishPackageDrafts (19786, POST /api/v1/packages/BASE/publish-drafts) — "a per-item publish ... publish-drafts holding such a draft"; the rollback door (~22820, on resolveOverlayPackageBinding's answer, i.e. the row's own binding — "a rollback of a row bound to the package"). The delete door asks only the package-less close (~23466) and NamedBaseWriteOperation excludes delete — "(a delete naming the package keeps the write)" holds, and protocol.named-base-overlay-write.test.ts:272 pins it. The conditions "where the pair can be ensured, the base records a version and the override write gate admits the declared body into the pair" are namedBaseCloseSubject's four exits (pair id forms, customizationPairEnsureRefusal undefined, manifest.version a non-empty string, overrideWriteVerdict not refuse) in the words of the #28905 changeset's Now refused list. The stored-rows clause ("package-less or bound to the read-only package itself") matches the census population since #29392. One omission is harmless: a body already carrying overrides is not this close's (the D2 gate's), which the entry need not say.

reason against the refusal text and the open doors. Holds. "a row bound to that package, declaring nothing, served in place of the package's own document" is the namedBaseOverlayWriteRefusal docblock; "refused 403 NOT_OVERRIDABLE naming the pair and the overrides declaration" is closedOverlayWriteClauses (pair with its ?package= address, declared = overrides: { package, basedOn }) under closedOverlayWriteError (code NOT_OVERRIDABLE, status 403); "a delete naming the package stays open, as the way to remove such a row, and so does every write the pair could not take, an email template locale the base does not ship among them" is the Still open list and the NamedBaseWriteOperation docblock. The sentence "The close reaches only what the command moves as a declared override" still scopes the package-less close only, which is what it says.

replacement against the cli and the census. Holds. "the preview names "bound to" that package and --json marks boundToBase" — packageless-overlays.ts line 392 prints bound to ${row.base} on a movable row and jsonRow emits boundToBase: true. The bound foreign_declaration remedy ("delete it naming the package, then save its body into the pair, declaring overrides: { package: BASE, basedOn }") is reasonOf's boundToBase arm word for word, and its premise ("a save naming the package is closed") is right: the save that would strip the key carries no overrides, so namedBaseCloseSubject judges it and closes it where the pair can take it. The multi_shipped clause, which still prescribes a delete naming no package, is correct as scoped: the census classifies a bound row by its binding as the one shipper, so a bound row is never multi_shipped (module header, "the rows bound to a read-only base"). The shadowed and no_pair clauses apply to both bindings unchanged. "every bound row it lists and does not move keeps its delete naming the package" is true of every bound disposition. The own-member exception reaches bound rows too (addedMemberOf runs for any movable row); the move deletes a bound row by its own binding (packageId: bound ? row.base : null), so "moves ... into the base's paired .custom" is one description for both.

Tried to break it; what held and what did not.

  • Could the entry prescribe a step the doors refuse? No: every remedy it names is either into the pair (?package=BASE.custom, open), a delete (open at both addresses where the ceremony does not move the row), or the ceremony itself.
  • Could an operator following it lose a customization? No: the bound remedy deletes only after the body is to be saved into the pair, in the order the cli prints, and the shadowed way out is the pair DELETE the entry already names.
  • Could the guide render break? The surface carries backticks and double quotes; the generator fences the code span by longest backtick run (upgrade-guide-surface-span.test.ts round-trips every real entry) and the dev's check:upgrade-guide run plus the green Test Core shards cover it. No pipe is introduced. Holds.

Findings:

  • S1 (should-fix, text the guide prints). Line 49 of the head file: 'packages ship the name):delete it naming no package, ...' — the space after the colon was dropped in this diff (main reads name): delete). It renders as name):delete in the generated upgrade guide. One-character fix in the same string.
  • S2 (should-fix, in the hits file). The header comment (lines 5-12) still describes the entry as "a write naming no package ... is refused" and "the package-less Setup rows those writes made", while the entry now registers both closed addresses and both kinds of stored row. A reader of the file (and the next card on it) is told half the entry. Two sentences in the comment; the PR body calls it out as outside the claimed text, but the claim amendment widened the surface to this file's replacement, so the file is the claim and its header should agree with it.
  • S3 (should-fix, precision). The failed-move tail: "the row may also be left, as it keeps its package-less write (a bound row, its delete naming the package)". For exactly the two failure causes that sentence names (the pair cannot be ensured; the base records no version), namedBaseCloseSubject returns null, so a bound row keeps its save naming the package as well, not only its delete — which the entry's own reason says ("so does every write the pair could not take"). True as written, narrower than the code; "(a bound row, its delete naming the package, and the save naming it where the pair could not take the override)" closes the gap.
  • Observation, no finding: acceptanceCriteria still names only the package-less control (the save naming no package answers 403); a parallel control naming the base would pin address (2), but the criterion as written is still a true and sufficient acceptance.

No must-fix. The three should-fixes are one-line text edits in the same file and do not move the verdict.

Implemented-by: claude/project-thread-y5cujk
Reviewed-by: session_01JQwxbahe4v7GBD94QeSnir

VERDICT: PASS


Generated by Claude Code

…e tail name both closes

Contract review should-fixes on the entry: restore the dropped space in the
multi_shipped remedy, the header comment names both closed addresses and both
kinds of stored row, and a bound row's failed-move tail also keeps the save
naming the package where the pair could not take the override. Text only.

Claude-Session: https://claude.ai/code/session_01JQwxbWytk9Ec5dXr38QrwZ
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir

Contract review — delta

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b99d86a144ed9c23d9170c5802a2f27ac480dd90
Prior-head: ecf1fd4a39fb1e0b188e276bfa3ba14c62eea2e7 — prior record 6585190070 (PASS, three should-fixes S1-S3, no must-fix)
Local-runs: none
Delta-scope: the REST compare ecf1fd4a...b99d86a1 is one commit (fix(spec): the packageless-overlay-write-closed header and failed-move tail name both closes), ahead 1, behind 0, one file: packages/spec/src/migrations/entries/semantic/18.packageless-overlay-write-closed.ts, +18/-12. The changeset is untouched. Cited code re-read on origin/main f966f018: no commit since the merge base 399df5bb touches protocol-packaged-base-refusal.ts, protocol.ts, protocol-packageless-overlay-migration.ts, the cli ceremony or protocol.named-base-overlay-write.test.ts, so the prior record's readings stand.

Per-finding closure:

  • S1 (dropped space, name):delete) — closed. The multi_shipped clause now reads name): delete it naming no package, ...; the three re-wrapped source lines carry the same characters as before plus the one space (the joined replacement string differs from the prior head by exactly that insertion and the S3 insertion below, nothing else).
  • S2 (stale header comment) — closed. The comment now names both addresses: the package-less write refused where the ceremony can move its row (the own-member exception kept), and "a save, publish or rollback naming the read-only package itself onto an item it ships is refused where the pair can take the declared override (a delete naming the package stays open)", and says the registration covers rows "package-less or bound to the read-only package itself" that the one operator step moves. Each clause is the entry's own surface/reason compressed; nothing in it goes beyond them.
  • S3 (failed-move tail understated a bound row's open doors) — closed. The tail now reads "(a bound row, its delete naming the package, and the save naming it where the pair could not take the override)", which is the wording the prior record asked for and is namedBaseCloseSubject's behaviour: for the two failure causes that sentence names (the pair cannot be ensured; the base records no version) it answers null, so the save naming the package is open.

New findings: none.

  • The new header's citation "ADR-0146 D1, D11" is accurate: on main the named-base close is the module section "ADR-0146 D1 / D11 — the write naming the read-only base itself is closed" (protocol-packaged-base-refusal.ts line 293), its docblock and both refusal sentences cite "(ADR-0146 D1, D11)" (lines 310, 332, 334); D11 alone was the package-less close's citation, and the comment now carries both closes, so adding D1 is right.
  • "a delete naming the package stays open" is accurate: NamedBaseWriteOperation excludes delete (line 298, "⛔ Not delete"), the delete door asks only the package-less close, and protocol.named-base-overlay-write.test.ts:272 pins "a delete naming the base removes a row bound to it (stored before the close)".
  • Text outside S1-S3: none changed. Field by field against the prior head, surface, reason and acceptanceCriteria are byte-identical; replacement differs only by the S1 space and the S3 insertion; the header comment is the S2 rewrite; id and the import are untouched.

Check-runs on b99d86a1 at review time: 65 total — 23 success, 11 skipped, 31 in_progress. Concluded green: Check Changeset, Governed Surface Queue Guard, PR-body guards, Spec property liveness, Lint & Repo Gates, every Lint family (CI/release/engine, ESLint, PM dispatch-gates, docs & code guards, repo & tooling, the three scoped lints), Type Check · source gates, Flag docs affected, Plan Test Core shards, Build UI Deps, Dogfood Regression Gate, Auto Label, Check PR Size, filter. Still running: Build Core, Test Core 1-8, Test UI 1-16, Type Check UI / workspace / consumer gates / debt ledger, Build & E2E UI, Live E2E UI. Skipped are the opt-in and conditional jobs (18.x upgrade harness, packed-tarball smoke, Build Docs, Lint UI, UI Published Artifacts, Temporal Conformance, Dogfood Verify CLI, the dogfood matrix shell, the schedule gate, plus Auto Label and Check PR Size on this re-run). Judged on what has concluded; the delta is a comment rewrite and 70 characters of string prose in a file whose text no test pins, and the prior head's Test Core and Test UI shards were green on the same surface, reason and near-identical replacement. The running shards are named, not assumed.

Implemented-by: claude/project-thread-y5cujk
Reviewed-by: session_01JQwxbahe4v7GBD94QeSnir

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir

维护者速读

  • 改了什么:只改文字。未发布的升级条目 packageless-overlay-write-closed 的 surface 与 reason 同时点名两个被关闭的覆盖写入地址(不带包的写入;点名只读基础包本身的保存、发布、回滚)及各自存下的行;replacement 补上绑定在基础包上的行的处置,与 CLI reasonOf 打印的一致;文件头注释同步。
  • 为什么改:metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905 在同一版本关闭了第二个地址,升级指南由此条目生成,原文只写了第一个(合同复核 6577747588 的 S3);p1,须在 18.3.0 切版前落地。
  • 风险与回滚:无运行时改动,只影响生成的升级指南与 spec-changes 文字(Clause-②: no),@objectstack/spec patch;回滚即 revert。
  • 先看:packages/spec/src/migrations/entries/semantic/18.packageless-overlay-write-closed.ts 的 surface(两个编号地址)与 replacement(绑定行的处置)。
  • 两分钟验证:pnpm --filter @objectstack/spec check:upgrade-guide,预期 exit 0。
  • 需要你做的:无,CI 全绿,席位落地。
  • 席位意见:合同复核 PASS(6585190070)提出的三处应修(丢失空格、头注释只写一半、失败迁移尾句窄于代码)已在 b99d86a 一次提交修完,同一复核者增量复核 PASS(6585399044)。

席位核对

ACCEPT · domain:spec#3 · head b99d86a144ed9c23d9170c5802a2f27ac480dd90 · 2026-11-02T19:07Z

  1. Shape: two files: packages/spec/src/migrations/entries/semantic/18.packageless-overlay-write-closed.ts (+55 / -29: surface, reason, replacement and the header comment; acceptanceCriteria untouched) and .changeset/29393-overlay-write-entry-both-addresses.md (@objectstack/spec patch, both declaration lines). Inside the claim (6583771832) as amended to the replacement text (6584492790).
  2. Premise, measured: the reviewer read the entry against namedBaseOverlayWriteRefusal / namedBaseCloseSubject in protocol-packaged-base-refusal.ts, the metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905 changeset's Now refused / Still open lists and the CLI reasonOf boundToBase arm on main; every clause matches. metadata-protocol: a commit revert or package-commit rollback can restore a row bound to a read-only base package that the #28905 close now refuses to write #29390 (commit revert) has no PR, so no revert line is added, as the card's conditional says.
  3. Gates: the dev reran on b99d86a: spec migration + upgrade-guide tests 226/226, check:upgrade-guide 0, check:spec-changes 0. On this head 68 check-runs, 57 success, 11 skipped, none failing; mergeable_state clean.
  4. Accepted deviations: check:dual-build-cjs-loads NOT MEASURED locally (prerequisite exit 3), left to CI, green. The PR body was rewritten by the seat to match the final tree (v2, read back identical).
  5. Out of scope: metadata-protocol: a commit revert or package-commit rollback can restore a row bound to a read-only base package that the #28905 close now refuses to write #29390's own ADR-0087 disposition decides whether address (2) later names the commit revert; it stays on that card. Skills-docs: n/a (Metadata-protocol: none).
  6. Landing: not governed (check-governed-merges --pr 29535: NOT governed, 95 changed lines), Clause-②: no, contract review at CONTRACT_REVIEW_TIER PASS (6585190070) plus delta PASS on this head (6585399044). No serial conflict: protocol.ts files untouched (freeze respected). Seat lands it: pr_ready + automerge_enable.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 19:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 1bdf674 Nov 2, 2026
69 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-y5cujk branch November 2, 2026 19:56
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir

Landed · 2026-11-02T20:23Z

Reading Value
PR #29535 MERGED 2026-11-02 19:56:24Z via merge queue, merge commit 1bdf674e on main
Card #29393 closed completed by Fixes #29393; no other closing keyword in the body

State change: pm:dispatched removed from #29393. Delivered: the unreleased upgrade entry packageless-overlay-write-closed names both closed overlay-write addresses, both kinds of stored row, and the remedy for a row bound to the read-only package. Remaining: #29390's own ADR-0087 disposition decides whether address (2) later names the commit revert; that stays on #29390.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(migrations): widen the unreleased ADR-0087 entry packageless-overlay-write-closed to name the base-named overlay write #28905 also closed

1 participant