Skip to content

Commit 150f740

Browse files
feat(spec)!: retire TemplateManifest publisher and isStarter — the platform decides both (#31178)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSi3Q621zfrgQodZHLx3cUop2)_ Fixes #31035 Clause-②: no (narrowing) — two authorable keys of `TemplateManifestSchema` become tombstones, and `os package publish` refuses the file's `publisher` by name Metadata-protocol: narrowed Skills-docs: none — no `skills/**` page teaches `objectstack.manifest.json`; the two `content/docs` pages that name these keys are updated here ## 维护者速读 - 改了什么:`objectstack.manifest.json` 里最后两个"写了不生效"的键 `publisher`、`isStarter` 改成 `retiredKey()` 墓碑,tsc 和 schema 解析都按键名拒绝并给出处方;blank 模板先去掉 `isStarter: true`,脚手架也会把这两个键从模板清单里剥掉;`os package publish` 遇到 `publisher` 直接拒绝,遇到 `isStarter` 只提示删除、照常发布。 - 为什么改:按你的裁决(#31035 评论 6708417668,选项 2):信任等级是平台按发布者凭据定的,作者自己写的值从来不生效,这种声明不该放在作者能改的文件里;`isStarter` 同理由平台决定。前五个键已由 #31064 退役,这是后一半。 - 风险与回滚:收窄(BREAKING 横幅 + adr-0087 登记)。还写着这两个键的旧清单在 schema 解析时被拒;之前从 blank 模板生成的项目都带 `isStarter: true`,所以发布时对它只警告不拒绝。hotcrm 的清单和服务模式发布脚本要另开卡把值挪进脚本(席位立卡)。回滚即 revert。 - 先看:`packages/spec/src/marketplace/template-manifest.zod.ts`(两个墓碑和处方);`packages/cli/src/commands/package/publish.ts`(`publisher` 拒绝、`isStarter` 并入删除警告);`packages/create-objectstack/src/templates/blank/objectstack.manifest.json`(去掉 `isStarter`)。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --project repo src/marketplace/template-manifest-inert-keys-retirement.test.ts`,预期 17 个用例全绿(七个键逐个拒绝、tsc 墓碑、两个 D3 登记、全树清单扫描)。 - 需要你做的:无,席位落地。 ## What changes - **Spec (`TemplateManifestSchema`).** `publisher` and `isStarter`, inherited from `CreatePackageRequestSchema`, are re-declared as `retiredKey()` tombstones on this file only. A parse refuses each at its own path with the prescription `` `TemplateManifest.<key>` was removed in @objectstack/spec 18 (ADR-0049) — … Delete the key. ``. The publish request's own `publisher` / `isStarter` and the server `Package` row are untouched. - **ADR-0087 bookkeeping.** Two `RETIRED_KEYS_BY_MAJOR[18]` rows (`marketplace/TemplateManifest:publisher`, `:isStarter`), one new D3 semantic entry `marketplace-template-manifest-platform-decided-keys-retired` (its own family: the reason differs from the five inert keys — these are platform decisions), and its `STEP18_RATIONALE` fragment (inserted where its id sorts, `order: 121`). No D2 conversion: this file is not a stack collection member (the `category` precedent). The sibling D3 entry's comment now points at the new family. - **Liveness.** Both `template_manifest` rows stay `dead` (tombstone route) with REMOVED notes and `verifiedAt` 2026-11-07; `_note` and the README row updated; counts unmoved. - **`os package publish`.** A manifest declaring `publisher` is refused before any network call (`retiredPublisherRefusal()`, the `visibility` shape). `isStarter` joins `RETIRED_INERT_MANIFEST_KEYS` and is named in the deletion warning, still publishing. `UNCARRIED_MANIFEST_KEYS` is now `['preview']`; `PLATFORM_DECIDED_MANIFEST_KEYS` and its warning branch are gone. Neither key is ever sent. - **create-objectstack.** The blank template drops `isStarter: true` (the ruling's first step). `RETIRED_TEMPLATE_MANIFEST_KEYS` gains both keys, so scaffolding strips them; the pin keeps that list equal to the spec's registry rows. - **Pins.** `template-manifest-inert-keys-retirement.test.ts` gains a top-level `describe` for the two keys (refusal per key, `isStarter: false` refused too, `@ts-expect-error` tsc pins), their registration and D3 entry, and its tree-scoped walk now covers all seven keys (same radius and `heldBy` registration as before). CLI pins: `publisher` refused with no network call plus a control; `isStarter` warned, not sent under either spelling; the key partition updated. - **Docs.** Marketplace handbook callout and the CLI reference paragraph; `template-manifest.mdx` regenerated, not hand-edited. - **Carried (comment 6701806800).** `taxonomy.zod.ts`'s module doc now says released values are append-only and retiring a released value is the ADR-0087 route; `taxonomy.mdx` regenerated. - **Changeset.** `@objectstack/spec` minor, `@objectstack/cli` minor, `create-objectstack` patch, with both declaration lines, BREAKING banner, FROM → TO per key and the adr-0087 marker. ## Why Maintainer ruling 6708417668 on #31035, verbatim in its binding part: "`publisher` and `isStarter` are retired from `objectstack.manifest.json` (`TemplateManifestSchema`) under ADR-0087, through the `spec-property-retirement` playbook." Measured on `origin/main` 9f61e06: both were schema members recorded `dead`; `os package publish` only named them in a warning and never sent them. ## Verification Final head after merging `origin/main` (8ef4d30) and a full `pnpm build` (119/119 tasks): see the `os-dev-report` comment on #31035 for the full record. <details><summary>Tests</summary> - `@objectstack/spec` `test` (local project): Test Files 820 passed (820), Tests 25936 passed, 1 todo; `test:repo`: Test Files 91 passed (91), Tests 1556 passed. Re-run on the merged head: the pin file 17/17, `src/marketplace` + `src/migrations` 13 files / 347 tests. - `@objectstack/spec` `check:generated` :: exit 0 (standalone); `check:test-typecheck` :: exit 0. - create-objectstack `vitest run`: 25 files / 374 tests passed; `typecheck` :: exit 0. - `@objectstack/cli` unit project: 394 of 396 files passed on the first run; the 2 red were `published-subpath-*.pin.test.ts` refusing a not-yet-built `packages/cli/dist` (prerequisite, not the diff) and pass after the full build, together with the three publish suites (5 files / 67 tests). - `packages/apps/connector-{docusign,github,stripe}` `pnpm test`: 28, 14 and 20 passed. - `eslint --no-inline-config` on every touched source file :: exit 0. </details> <details><summary>Derived gates</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 114 families, identical before and after the merge; results and the `--ran` reconciliation are in the report comment. </details> ## Acceptance notes - **Design choice the seat may strike:** `publisher` is refused by `os package publish`, `isStarter` only warned. This mirrors #31064 (`visibility` refused; keys the blank template shipped warned): no scaffold ever wrote `publisher`, so refusing it strands no project, while every project scaffolded before this release carries `isStarter: true`. The schema parse refuses both either way. - `RETIRED_INERT_MANIFEST_KEYS` keeps its name although it now holds `isStarter`; its doc comment says what the list is ("never changed what this command published"). - **Out of scope, for the seat to file:** hotcrm's `objectstack.manifest.json` (writes `publisher` / `isStarter`) and `scripts/lib/marketplace-listing.mjs` (`packageUpsertBody` reads them off the manifest) move both values into the script, per the ruling. Not read from this container. - `.github/workflows/release.yml` (governed) is untouched. - Fable full review: PASS with should-fix, all fixed; two delta reviews (verdict comment on this PR). ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes (touched files locally; repo scan is CI's). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [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. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017ihL2dLTDTsCKoixfFLh7U --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent bd5a443 commit 150f740

24 files changed

Lines changed: 305 additions & 112 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/cli': minor
4+
'create-objectstack': patch
5+
---
6+
7+
feat(spec)!: retire `objectstack.manifest.json` `publisher` and `isStarter` — the platform decides both (#31035)
8+
9+
Clause-②: no (narrowing) — two authorable keys of `TemplateManifestSchema` become tombstones, and `os package publish` refuses the file's `publisher` by name
10+
Metadata-protocol: narrowed
11+
12+
<!-- adr-0087: registered marketplace-template-manifest-platform-decided-keys-retired -->
13+
14+
**BREAKING** for an `objectstack.manifest.json` that still writes `publisher` or `isStarter`, and for `os package publish` run on a manifest that declares `publisher`.
15+
16+
- **Retired (ADR-0049 enforce-or-remove).** `TemplateManifest.publisher` and `TemplateManifest.isStarter` are `retiredKey()` tombstones: `tsc` refuses them and a parse of `TemplateManifestSchema` refuses each with its prescription. Neither ever had an effect from this file: `publisher` is the trust tier that gates listing privileges, which the platform assigns from the publisher's credentials, and the platform honours a starter flag only on a first-party seed; `os package publish` never sent either. A trust claim does not belong in an author-editable file. `RETIRED_KEYS_BY_MAJOR[18]` lists both keys and the upgrade guide carries the semantic entry `marketplace-template-manifest-platform-decided-keys-retired`. No ADR-0087 conversion: this file is not a stack collection member, so `os migrate meta` has nothing to rewrite; a runtime boot never reads it. The publish request's own `publisher` and `isStarter` (`CreatePackageRequestSchema`) are unchanged.
17+
- **Migration** — FROM → TO, in `objectstack.manifest.json`:
18+
- `publisher` → delete it. The platform assigns a package's trust tier from the credentials it is published with.
19+
- `isStarter` → delete it. The platform decides which packages are starters; a first-party script that seeds one in service mode carries the flag itself, not in the manifest.
20+
- **`@objectstack/cli`.** `os package publish` refuses a manifest declaring `publisher` before any network call. It names `isStarter` in the deletion warning with `name`, `specVersion`, `skills` and `scaffold`, and still publishes, because every project scaffolded from the blank template before this release carries `isStarter: true`. Neither key is ever sent.
21+
- **`create-objectstack`.** The blank template's manifest drops `isStarter`, and scaffolding strips both keys (with the other retired ones) from a template's manifest, so a template authored before this release still scaffolds a manifest the schema accepts.

‎content/docs/deployment/cli.mdx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3315,8 +3315,10 @@ checked against the spec's schema before anything is sent, and a declared
33153315
install mode, a `connector` a declared connector. The retired `category` — in
33163316
the file, or as `--category` — is refused before any network call, with the
33173317
facets that replace the value it held, and so is a `visibility` in the file
3318-
(pass `--visibility`). The retired `name`, `specVersion`, `skills` and
3319-
`scaffold`, which nothing ever read, are named in a warning for deletion. `tags` has no flag and is
3318+
(pass `--visibility`) and a `publisher` in the file (the platform assigns the
3319+
trust tier from your credentials). The retired `name`, `specVersion`, `skills`,
3320+
`scaffold` and `isStarter`, which never changed what publish sent, are named in
3321+
a warning for deletion. `tags` has no flag and is
33203322
sent as written: the control plane judges the list, and a refusal is its 400,
33213323
printed as it is. A top-level `tagline` is used only as the description, when
33223324
neither `--description` nor the file's `description` gives one; otherwise it is

‎content/docs/deployment/marketplace-handbook.mdx‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ columns for a missing key. The per-locale shape is the
9292
**Two things the file can say that never reach the listing.** Publish uploads no
9393
screenshot images, and the Console's marketplace pages render none: a
9494
`preview.screenshots` list stays on your disk, and `screenshotCaptions` travels
95-
with no image to caption. The schema also accepts `publisher` and `isStarter`,
96-
but the platform decides both: an author's value has no effect, so
97-
`os package publish` never sends them and prints a warning naming each one the
98-
file declares. `tags` is different: it is sent as written (see the table above).
99-
The file no longer accepts `visibility` (pass `--visibility`; publish refuses the
100-
key by name) nor `name`, `specVersion`, `skills` or `scaffold`, which nothing
101-
ever read: publish names each one still present so you can delete it.
95+
with no image to caption. `tags` is different: it is sent as written (see the
96+
table above). The file no longer accepts `visibility` (pass `--visibility`;
97+
publish refuses the key by name), nor `publisher` — the platform assigns a
98+
package's trust tier from your credentials, and publish refuses the key by name —
99+
nor `isStarter`, `name`, `specVersion`, `skills` or `scaffold`, which never
100+
changed what publish sent (the platform decides which packages are starters):
101+
publish names each one still present so you can delete it.
102102
Put the icon, the description, the tags, the tagline and the README where the
103103
table above says, and nothing else.
104104
</Callout>

‎content/docs/references/marketplace/taxonomy.mdx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ of `SelectOptionSchema.value`): words joined by `_`, never `-`, so a select
3838
field — the cloud catalog's facet columns, a console filter — can carry the
3939
facet as its options unchanged.
4040

41-
Values are append-only. Renaming or removing one is an ADR-0087 retirement
42-
with a data migration. Adding one needs a real listed package, or a ruled
43-
scenario card, that needs it, and the console's labels in the same change.
41+
Released values are append-only. Renaming or removing a released value is
42+
an ADR-0087 retirement with a data migration; a value no release has
43+
shipped yet can still be respelled without one. Adding one needs a real
44+
listed package, or a ruled scenario card, that needs it, and the console's
45+
labels in the same change.
4446
Keep each facet near a dozen values; merge before you grow.
4547

4648
## What is deliberately absent

‎content/docs/references/marketplace/template-manifest.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ objectstack.manifest.json — template / package source descriptor
5454
| **iconUrl** | `string` | optional | |
5555
| **homepageUrl** | `string` | optional | |
5656
| **license** | `string` | optional | |
57-
| **publisher** | `Enum<'objectstack' \| 'partner' \| 'community' \| 'private'>` | optional | Package publisher provenance tier |
58-
| **isStarter** | `boolean` | optional | |
57+
| **publisher** | `never` | optional | [REMOVED] `TemplateManifest.publisher` was removed in @objectstack/spec 18 (ADR-0049) — the platform assigns a package's publisher trust tier from the publisher's credentials, so a value written here never had an effect and `os package publish` never sent it. Delete the key. |
58+
| **isStarter** | `never` | optional | [REMOVED] `TemplateManifest.isStarter` was removed in @objectstack/spec 18 (ADR-0049) — the platform decides which packages are starters (honoured only on a first-party seed), so a value written here never had an effect and `os package publish` never sent it. Delete the key. |
5959
| **translations** | `Record<string, { displayName?: string; description?: string; readme?: string; tagline?: string; … }>` | optional | Locale-keyed overrides; missing keys fall back to base columns |
6060
| **manifestId** | `string` | optional | Optional declarative default for the published package id (reverse-domain, e.g. com.acme.crm). Absent on a template source tree: `objectstack package publish` falls back to --manifest-id and then to a derived `local.<slug>`. NOT optional on the publish request itself |
6161
| **name** | `never` | optional | [REMOVED] `TemplateManifest.name` was removed in @objectstack/spec 18 (ADR-0049) — nothing read it: `os package publish` addresses the package by `manifestId` (or derives one from the compiled artifact), and a project's slug is its `package.json` `name`. Delete the key. |

‎packages/apps/connector-docusign/test/listing.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ describe('connector-docusign listing', () => {
105105
});
106106

107107
it('declares no key the publisher ignores', () => {
108-
// The handbook: the platform decides `publisher` and `isStarter`; the CLI never sends them.
108+
// Retired from the file: the platform decides `publisher` and `isStarter`, and the schema refuses both.
109109
expect(manifest).not.toHaveProperty('publisher');
110110
expect(manifest).not.toHaveProperty('isStarter');
111111
});

‎packages/apps/connector-github/test/listing.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('connector-github listing', () => {
7171
});
7272

7373
it('declares no key the publisher ignores', () => {
74-
// The handbook: the platform decides `publisher` and `isStarter`; the CLI never sends them.
74+
// Retired from the file: the platform decides `publisher` and `isStarter`, and the schema refuses both.
7575
expect(manifest).not.toHaveProperty('publisher');
7676
expect(manifest).not.toHaveProperty('isStarter');
7777
});

‎packages/apps/connector-stripe/test/listing.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('connector-stripe listing', () => {
8282
});
8383

8484
it('declares no key the publisher ignores', () => {
85-
// The handbook: the platform decides `publisher` and `isStarter`; the CLI never sends them.
85+
// Retired from the file: the platform decides `publisher` and `isStarter`, and the schema refuses both.
8686
expect(manifest).not.toHaveProperty('publisher');
8787
expect(manifest).not.toHaveProperty('isStarter');
8888
});

‎packages/cli/src/commands/package/publish.ts‎

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -367,13 +367,28 @@ export function retiredVisibilityRefusal(): string {
367367
}
368368

369369
/**
370-
* Retired `objectstack.manifest.json` keys nothing ever read (ADR-0049,
371-
* #31035): a parse of the file refuses each one, so this command names them
372-
* for deletion. A warning, not a refusal: every project scaffolded from the
373-
* blank template before the retirement carries them, and they never changed
374-
* what was published.
370+
* The refusal for a manifest that still declares the retired `publisher`
371+
* (ADR-0049, #31035). The platform assigns a package's trust tier from the
372+
* publisher's credentials, so this command never sent the file's value and an
373+
* author's claim never took effect. Refused, like `visibility`, before any
374+
* network call: an author who wrote it meant to claim a trust tier, and
375+
* publishing without saying so would hide that the claim was never made.
375376
*/
376-
export const RETIRED_INERT_MANIFEST_KEYS = ['name', 'specVersion', 'skills', 'scaffold'] as const;
377+
export function retiredPublisherRefusal(): string {
378+
return 'objectstack.manifest.json declares `publisher`, which was removed in @objectstack/spec 18: '
379+
+ 'the platform assigns a package\'s publisher trust tier from the publisher\'s credentials, and '
380+
+ '`os package publish` never sent it. Delete it.';
381+
}
382+
383+
/**
384+
* Retired `objectstack.manifest.json` keys that never changed what this
385+
* command published (ADR-0049, #31035): a parse of the file refuses each one,
386+
* so this command names them for deletion. A warning, not a refusal: every
387+
* project scaffolded from the blank template before the retirement carries
388+
* them (`isStarter: true` included — the platform decides the starter flag and
389+
* this command never sent it).
390+
*/
391+
export const RETIRED_INERT_MANIFEST_KEYS = ['name', 'specVersion', 'skills', 'scaffold', 'isStarter'] as const;
377392

378393
/** The {@link RETIRED_INERT_MANIFEST_KEYS} that `manifest` declares, in list order. */
379394
export function retiredInertManifestKeys(manifest: Record<string, unknown>): string[] {
@@ -382,37 +397,28 @@ export function retiredInertManifestKeys(manifest: Record<string, unknown>): str
382397

383398
/**
384399
* Keys `objectstack.manifest.json` accepts that this command does NOT carry to
385-
* the control plane. `publisher` and `isStarter` are inherited from
386-
* `CreatePackageRequestSchema`; `preview` is a scaffold-side extra (publish
387-
* has no screenshot upload path).
388-
*
389-
* - `publisher` and `isStarter` are {@link PLATFORM_DECIDED_MANIFEST_KEYS}:
390-
* the hosted control plane decides both and never reads an author's value
391-
* (`publisher` is a trust tier that gates listing privileges; `isStarter` is
392-
* honoured only for a first-party seed). ⛔ Never send them: a value
393-
* self-declared in an author's file is a claim only the platform can judge.
394-
* - `preview` is not carried by the hosted catalog yet; that is tracked at
395-
* {@link CATALOG_SEND_ISSUE}.
400+
* the control plane. `preview` is a scaffold-side extra (publish has no
401+
* screenshot upload path) and is not carried by the hosted catalog yet; that
402+
* is tracked at {@link CATALOG_SEND_ISSUE}.
396403
*
397-
* `visibility` is no longer here: the file's key is retired and refused
398-
* ({@link retiredVisibilityRefusal}); the `--visibility` flag carries it.
404+
* `visibility`, `publisher` and `isStarter` are no longer here: the file's
405+
* keys are retired. `visibility` is refused ({@link retiredVisibilityRefusal};
406+
* the `--visibility` flag carries it), `publisher` is refused
407+
* ({@link retiredPublisherRefusal}), and `isStarter` is named for deletion
408+
* ({@link RETIRED_INERT_MANIFEST_KEYS}). ⛔ Never send `publisher` or
409+
* `isStarter` from the file: both are claims only the platform can judge.
399410
*
400411
* `tags` is not here: the hosted route validates and stores it, so it is sent
401412
* (see the request body in `run()`).
402413
*
403414
* Dropping an authored key silently is the defect, so each one the file
404-
* declares is named in a warning. The warning does not fail the publish: the
405-
* bundled blank template ships `isStarter: true`, and refusing it would break
406-
* every project scaffolded from it.
415+
* declares is named in a warning. The warning does not fail the publish.
407416
*
408417
* Every `TemplateManifestSchema` key is classified in
409418
* `test/package-publish-uncarried-manifest-keys.test.ts`, so a key the schema
410419
* gains later goes red there until publish either sends it or lists it here.
411420
*/
412-
export const UNCARRIED_MANIFEST_KEYS = ['publisher', 'isStarter', 'preview'] as const;
413-
414-
/** The {@link UNCARRIED_MANIFEST_KEYS} the platform decides, whatever the file says. */
415-
const PLATFORM_DECIDED_MANIFEST_KEYS: readonly string[] = ['publisher', 'isStarter'];
421+
export const UNCARRIED_MANIFEST_KEYS = ['preview'] as const;
416422

417423
/** Where carrying the remaining catalog keys to the hosted control plane is tracked. */
418424
export const CATALOG_SEND_ISSUE = 'https://github.com/objectstack-ai/objectstack/issues/27348';
@@ -423,24 +429,14 @@ export function uncarriedManifestKeys(manifest: Record<string, unknown>): string
423429
}
424430

425431
/**
426-
* The warnings for the {@link uncarriedManifestKeys} a manifest declares: one
427-
* for the platform-decided keys (an author's value has no effect), and one for
428-
* the catalog keys the hosted side does not carry yet — then one for the
432+
* The warnings for the {@link uncarriedManifestKeys} a manifest declares — one
433+
* for the catalog keys the hosted side does not carry yet — then one for the
429434
* {@link retiredInertManifestKeys} it still declares. Empty when none.
430435
*/
431436
export function uncarriedManifestWarnings(manifest: Record<string, unknown>): string[] {
432-
const declared = uncarriedManifestKeys(manifest);
437+
const catalog = uncarriedManifestKeys(manifest);
433438
const named = (keys: string[]) => keys.map((key) => `\`${key}\``).join(', ');
434-
const platform = declared.filter((key) => PLATFORM_DECIDED_MANIFEST_KEYS.includes(key));
435-
const catalog = declared.filter((key) => !PLATFORM_DECIDED_MANIFEST_KEYS.includes(key));
436439
const warnings: string[] = [];
437-
if (platform.length > 0) {
438-
const one = platform.length === 1;
439-
warnings.push(
440-
`objectstack.manifest.json declares ${named(platform)}, which ${one ? 'is' : 'are'} decided by the `
441-
+ `platform: an author's value has no effect, so \`os package publish\` does not send ${one ? 'it' : 'them'}.`,
442-
);
443-
}
444440
if (catalog.length > 0) {
445441
const one = catalog.length === 1;
446442
warnings.push(
@@ -455,8 +451,8 @@ export function uncarriedManifestWarnings(manifest: Record<string, unknown>): st
455451
const one = retired.length === 1;
456452
warnings.push(
457453
`objectstack.manifest.json declares ${named(retired)}, which ${one ? 'was' : 'were'} removed in `
458-
+ `@objectstack/spec 18: nothing ever read ${one ? 'it' : 'them'}, and a parse of the file now refuses `
459-
+ `${one ? 'it' : 'them'}. Delete ${one ? 'it' : 'them'}.`,
454+
+ `@objectstack/spec 18: ${one ? 'it' : 'they'} never changed what \`os package publish\` sent, and a `
455+
+ `parse of the file now refuses ${one ? 'it' : 'them'}. Delete ${one ? 'it' : 'them'}.`,
460456
);
461457
}
462458
return warnings;
@@ -701,6 +697,11 @@ export default class PackagePublish extends Command {
701697
this.exit(1);
702698
return;
703699
}
700+
if (m.publisher !== undefined) {
701+
printError(retiredPublisherRefusal());
702+
this.exit(1);
703+
return;
704+
}
704705
const taxonomy = readPublishTaxonomy(m, artifact);
705706
if (!taxonomy.ok) {
706707
printError(taxonomy.message);

0 commit comments

Comments
 (0)