|
| 1 | +--- |
| 2 | +'@objectstack/cloud-connection': minor |
| 3 | +--- |
| 4 | + |
| 5 | +fix(cloud-connection): `POST /api/v1/marketplace/install-local` parses the package id it installs through the manifest declaration (#19576) |
| 6 | + |
| 7 | +Clause-②: no (narrowing) |
| 8 | + |
| 9 | +**BREAKING for callers of the install-local door** — a manifest whose `id` is |
| 10 | +not reverse-domain notation is now refused `PLUGIN_MANIFEST_INVALID` (`400` for |
| 11 | +an inline manifest, `502` for a cloud-fetched snapshot) before anything is |
| 12 | +registered or written. It used to install and answer `200`. |
| 13 | + |
| 14 | +The accept set only shrinks back to what the published declaration has always |
| 15 | +said. `MANIFEST_ID_PATTERN` (`@objectstack/spec/kernel`) is the one declaration |
| 16 | +of a package id, and the other two package-install doors — `POST |
| 17 | +/api/v1/packages` and the protocol install primitive — already refuse the ids it |
| 18 | +refuses. This door is a separate path: it never calls the protocol primitive, |
| 19 | +so neither gate covered it. It derived the id as `manifest.id ?? manifest.name` |
| 20 | +and parsed nothing, so `late-app`, `com.example.my_erp`, a number, or a manifest |
| 21 | +carrying only a `name` installed cleanly and became the key for the on-disk |
| 22 | +ledger entry and for every `:manifestId` route. |
| 23 | + |
| 24 | +The door now asks the declaration **by reference** — `ManifestSchema.shape.id` |
| 25 | +— at the one point where the inline branch (after a compiled bundle is |
| 26 | +flattened) and the cloud branch have converged, ahead of the `409 |
| 27 | +MANIFEST_CONFLICT` collision check, the posture gate, the hot-register and the |
| 28 | +ledger write. The sentence the caller reads is the declaration's own |
| 29 | +(`manifestIdRefusal`), surfaced rather than reworded. Posting `id: 'late-app'` |
| 30 | +now answers, in `error.message`: |
| 31 | + |
| 32 | +```text |
| 33 | +Invalid package id 'late-app' on `manifest.id`. Expected reverse-domain notation |
| 34 | +('com.steedos.crm', 'org.apache.superset') — lowercase dot-separated segments; |
| 35 | +hyphens allowed inside a segment, underscores are not. Did you mean |
| 36 | +'com.example.late-app'? |
| 37 | +``` |
| 38 | + |
| 39 | +**`manifest.name` is no longer read as an id.** `ManifestSchema` declares `id`; |
| 40 | +`name` is a display label with no pattern. A manifest with no `id` is refused |
| 41 | +with the same sentence, naming `manifest.id`. The inline branch's earlier |
| 42 | +message for that case — which said the manifest needed an `"id"` or a `"name"` |
| 43 | +— is gone with the fallback it described. |
| 44 | + |
| 45 | +**What is not affected.** A conforming id installs exactly as before, on both |
| 46 | +branches and for both the flat and the compiled-bundle shape. Ledger entries |
| 47 | +already on disk are not re-judged: an entry an older build installed under an |
| 48 | +id the declaration refuses still rehydrates at boot and can still be removed |
| 49 | +with `DELETE /api/v1/marketplace/install-local/:manifestId`; only a fresh |
| 50 | +install under that id is refused. Boot-time and in-process registration |
| 51 | +(`manifest.register`, `AppPlugin`) never passes through this door. |
| 52 | + |
| 53 | +**If you are refused.** Give the manifest an `id` in reverse-domain notation — |
| 54 | +lowercase dot-separated segments, hyphens allowed inside a segment, underscores |
| 55 | +not. The refusal names the key, echoes what was sent and, where a mechanical |
| 56 | +repair exists, offers one it has already checked against the rule. Artifacts |
| 57 | +built by `os build` from `defineStack()` already carry a conforming id, because |
| 58 | +the same declaration refuses anything else at build time. |
| 59 | + |
| 60 | +<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable is removed, renamed or reshaped: no spec key, no export, no stored row. `objectstack migrate meta` has nothing to reach, because there is no old spelling that maps to a new one — a caller supplies an id the declaration already required, and an id's repair changes the package's identity, so no mechanical mapping could be prescribed even in principle. Existing ledger entries are left exactly as they are. The refusal itself carries the remedy. --> |
0 commit comments