You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 680ded0
Browse filesBrowse the repository at this point in the historyBrowse files
fix(cli): parse os package publish manifest ids through PackageSchema.manifestId (#16889)
`packages/cli/src/commands/package/publish.ts` carried its own
`MANIFEST_ID_RE` and tested `--manifest-id` (and the derived id) against it,
while the contract this repo declares for the column it publishes into is
`PackageSchema.manifestId` in `packages/spec/src/cloud/package.zod.ts`. The
local copy was looser on every axis, so the preflight admitted what the
control plane refuses.
- Delete `MANIFEST_ID_RE`. Both paths — the explicit `--manifest-id` /
`objectstack.manifest.json` check and the derive path in
`deriveManifestId` — now parse through the imported schema.
- The derive path's extra `explicit.includes('.')` condition is dropped: the
schema subsumes it (its pattern needs at least two segments). That
condition is why the two paths disagreed with each other as well as with
the declaration.
- The refusal text is quoted from the schema's own `invalid_format` issue and
its `.describe()`, so it can no longer state a contract that does not exist.
- A derived id the schema rejects (`slugify` has no letter-first rule, so an
app named `2024 App` derives `local.2024-app`) is refused before any network
call, naming where the id came from and how to set one. It is deliberately
not normalised: `manifestId` is immutable once published.
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
Co-authored-by: Claude <noreply@anthropic.com>
`os package publish` now decides what a manifest id is by parsing it through `PackageSchema.manifestId` — the schema for the very column it publishes into — instead of testing it against a hand-copied look-alike.
6
+
7
+
The command carried its own rule (`MANIFEST_ID_RE`, a case-insensitive "starts alphanumeric, then any of a-z 0-9 dot underscore hyphen, up to 255 chars"), which is looser than the declared contract on every axis. The local preflight therefore **admitted what the control plane refuses**: a single segment (`crm`), an underscore (`com.acme.repair_desk`), upper case (`COM.ACME.CRM`), a digit-first segment (`9foo.bar`), an empty segment (`com..acme`) and a trailing dot (`com.acme.`). The preflight passed, the request went out, and the server answered `400`. Its error text, when it did fire, named a contract (`a-z0-9._-`) that does not exist — so a user who followed the message walked into a second refusal.
8
+
9
+
-**One rule, both paths.**`MANIFEST_ID_RE` is deleted. The explicit `--manifest-id` / `objectstack.manifest.json` path and the derive path (`deriveManifestId`, which adopts `artifact.manifest.id`) now ask the same imported schema. They previously disagreed with each other as well as with the declaration: the derive path additionally required a dot, so a bare `crm` was blocked there and accepted on the explicit path. That extra condition is gone because the schema subsumes it — its pattern requires at least two segments.
10
+
-**The refusal text is quoted from the schema**, from its own `invalid_format` issue plus its `.describe()`, so it can no longer drift from the rule it describes.
11
+
-**A derived id the schema rejects is refused, not rewritten.**`slugify` has no letter-first rule, so an app named `2024 App` derives `local.2024-app` — digit-first, and rejected. That is now refused before any network call, with a message naming where the id came from and how to set one (`--manifest-id`, `manifestId` in `objectstack.manifest.json`, or `manifest.id`). It is deliberately not normalised into some other id: `manifestId` is immutable once published, and minting a different permanent global identifier than the inputs imply is worse than saying what is wrong.
12
+
13
+
Publishing is unaffected for every id the control plane accepts — a legal reverse-domain id passes both paths with unchanged bytes. What changes is that the ids the server was going to reject are now refused locally, with the real rule in the message.
0 commit comments