Repository navigation
Commit 4bce21d
feat(adr-0087): retire a deleted published package whole (
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8AiiSUKX6yjeRe7S4FPtjpW)_
Fixes #26220
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:ADR-0087 D9 加整包删除条款:已发布的包在 HEAD 被删除时,`runtime-export-retired`
的操作数写成 `包名#*`;门禁 `check-adr-0087-registration` 加对应谓词,自测新增 WP 电池 55 条。
- 为什么改:执行 PR #26365 评论 6439038287 的裁决 A(维护者「同意」)。被删的包在 HEAD
没有入口可读,`@object-ui/plugin-ai` 的下线(#26365)在这个门禁上会一直红。
- 风险与回滚:只放行整包删除这一种新形态,而且条件要全部成立:基点上是已发布的工作区包,HEAD 上没有
`package.json`、`pnpm-workspace.yaml` 不再列它的目录、不在 changesets 的 fixed
组。其他判定不变,原有 627 条自测照样全绿。回滚即 revert 本 PR。
- 先看:`docs/adr/0087-metadata-protocol-upgrade-contract.md` 里 D9 下面的第二条
2026-10-27 修订说明;`scripts/check-adr-0087-registration.mjs` 的
`verifyWholePackageRetired` 和谓词 2 的整包分支(`packageReference`)。
- 两分钟验证:`node scripts/check-adr-0087-registration.mjs --self-test`,预期以 0
退出并打印 `682 assertions`。
- 需要你做的:无。本 PR 等主任席位的契约复核(`CONTRACT_REVIEW_TIER`)PASS 和席位
ACCEPT,在那之前不需要你操作。
## What changes
**ADR-0087 D9, amended (Tier H).** A second dated note under D9's
unmappable-entry paragraph, plus a Status-line entry, records the ruling
on PR #26365 (comment `6439038287`, letter A, the maintainer's 「同意」
2026-10-27T10:07Z). A published package deleted at HEAD is retired whole
with a third operand kind, `PACKAGE#*`:
- **Predicate 1 (`retired-from-entry`)** is proven about the package. At
the merge base it must be a workspace member, not `private`, and its
published entry must map as D9 already requires. At HEAD it must be
absent from the workspace in all of the ruling's three senses: no
`package.json` (the base manifest is gone, and no workspace manifest
carries the name, so a package that moved or was renamed is not
deleted), no `pnpm-workspace.yaml` glob naming its directory literally,
and no `fixed` group in `.changeset/config.json` listing it. A missing
or unreadable config is refused, never read as an empty group.
- **Predicate 2 (`no-metadata-surface`)** asks the package what it asks
an export, at the merge base: none of the package's files is a metadata
surface, and no metadata surface outside it names it in code (comment
spans masked): an import specifier, by its root or a subpath, or any
other occurrence of the package name, a string value included.
- **Predicate 3 (`from-operands-named`)**: the FROM → TO block names the
package on a single line, not one per export, by its package name. Its
exports take no line of their own.
An export or member operand on a deleted package is still refused at
predicate 1, and the refusal now names the `PACKAGE#*` spelling. The
closed-vocabulary blocks of the 2026-08-13 and 2026-10-12 addenda list
the third marker line, and the 2026-10-12 addendum now reads "three
operand kinds".
**`scripts/check-adr-0087-registration.mjs`.**
- `parseExportOperand` accepts `PACKAGE#*`.
- `verifyWholePackageRetired` sits beside `sourceEntryAt` and holds
predicate 1's package proof. It names every HEAD condition that fails,
not only the first.
- `packageReference` and a whole-package branch hold predicate 2. It
refuses on the first comment-masked occurrence of the package name
outside the package's own files, and reports an import specifier as
IMPORTS.
- Predicate 3 reads a FROM operand equal to a `#*` package's name as
that operand.
- The header comment carries the new operand.
## Why
The ruling closes the last open question on #26220. Without the clause,
a whole-package retirement could only land red. Its alternatives were
`registered`, which would name a conversion that never happens, and
`not-required (unpublished)`, which reads `private: true` at HEAD, a
field a deleted package cannot carry.
## Tests
`--self-test` at `994c3279`: exit 0, `682 assertions over real temp git
repos` (627 at base `dc9d1ff8`). The new battery `WP (#26220)` has 55
assertions, and the roster floor goes from 57 to 58 batteries. Each red
differs from WP-G1 in one respect.
<details><summary>WP cases</summary>
- G1: the #26365 shape. G1b: the verdict names `#*` and the manifest.
G2: the one line written as an arrow removal. G3: a metadata surface
that names the package only in comments.
- R1: still in the fixed group. R2: `private: true` at base. R3: not a
workspace member at base. R4: not deleted. R5: moved to another
directory under the same name. R6: `pnpm-workspace.yaml` still names the
directory. R7: no changesets config at HEAD. R8: no mappable published
entry at base.
- R9: the package carries a `*.zod.ts`. R10: a metadata surface outside
it imports it.
- R11: no FROM line. R12: per-export FROM lines instead of the package
line. R13: an export operand on the deleted package points at `#*`.
- R14: a metadata surface outside the package names it as a string
value, with no import.
- U1–U5: operand grammar; `packageReference` reads a subpath import,
skips the package's own files, does not match a name that only shares
the prefix (`plugin-xy`), and reports a string-value mention with its
line while skipping a comment.
</details>
**First consumer, measured with this branch's gate against `origin/main`
`dc9d1ff8`:**
- PR #26365's head `bde1e722`, unchanged: exit 1. Each of its three
export operands is refused at HEAD, and each refusal now names
`@object-ui/plugin-ai#*`.
- A local probe commit on that head that changes only its changeset (the
marker becomes `runtime-export-retired @object-ui/plugin-ai#*`, and its
three per-export FROM lines become one ``- FROM `@object-ui/plugin-ai`
TO …`` line): exit 0, `verified: @object-ui/plugin-ai#* (package,
ui/packages/plugin-ai/package.json)`.
So #26365 needs exactly those two changeset edits. They belong to that
PR and are not made here.
## Acceptance notes
- **Wording beyond the ruling's text.** The ruling fixes predicate 1's
proof and predicate 3's one line. It says nothing about predicate 2. D9
requires all three predicates for every operand, so the note restates
D9's own predicate 2 for the package: no metadata-surface file in the
package, and no metadata surface outside it naming the package in code
(contract review 6457699551, must-fix 1). "Its exports take no line of
their own" follows from predicate 3's existing "exactly the named
operands".
- **The fixed group is read literally.** Only `fixed` is read, as the
ruling names it. A glob entry names no package, and `linked` / `ignore`
are not read.
- **No ablation.** os-dev excludes scripts from ablation. Each red
asserts the specific refusal text it expects, and #26365's before/after
pair above is the end-to-end witness.
- **No changeset.** Nothing publishes: the root package is `private`,
and `docs/adr/**` is not shipped. The PR carries `skip-changeset`.
## Verification
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `994c3279` (merge base `dc9d1ff8`, 2
paths): 44 commands derived, 44 run, all exit 0. Each ran with stdin
from `/dev/null`, and each exit was captured before any pipe.
`check:doc-formula-expressions` ran after `turbo run build` of
`@objectstack/formula` and `@objectstack/lint` under the verify lock.
`--ran` reconciliation: 44 derived, 44 run, 0 NOT-MEASURED, 0 UNRUN. No
test file names the edited script (`git grep` over `*.test.*` /
`*.spec.*`: 0 hits).
Tier H (`docs/adr/**`). The director seat's contract review at
`CONTRACT_REVIEW_TIER` is PASS on `994c3279` (comment 6458009682), after
a FAIL on `75285daf` (6457699551). Landing waits for the seat's ACCEPT.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No `packages/spec` change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (No formatter ran; lint
is CI's run.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset. (This one
publishes nothing: `skip-changeset`.)
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (维护者速读 is Chinese by
house rule.)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbX6swE1AuYRHTZsPqo)_
---------
Co-authored-by: Claude <noreply@anthropic.com>PACKAGE#*, D9 amendment) (#26766)1 parent 4daad78 commit 4bce21d
2 files changed
Lines changed: 335 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
753 | 753 | | |
754 | 754 | | |
755 | 755 | | |
| 756 | + | |
756 | 757 | | |
757 | 758 | | |
758 | 759 | | |
| |||
763 | 764 | | |
764 | 765 | | |
765 | 766 | | |
766 | | - | |
767 | | - | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
768 | 770 | | |
769 | 771 | | |
770 | 772 | | |
| |||
1035 | 1037 | | |
1036 | 1038 | | |
1037 | 1039 | | |
1038 | | - | |
1039 | | - | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
1040 | 1043 | | |
1041 | 1044 | | |
1042 | 1045 | | |
1043 | 1046 | | |
| 1047 | + | |
1044 | 1048 | | |
1045 | 1049 | | |
1046 | 1050 | | |
| |||
1139 | 1143 | | |
1140 | 1144 | | |
1141 | 1145 | | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
1142 | 1186 | | |
1143 | 1187 | | |
1144 | 1188 | | |
| |||
0 commit comments