|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +'@objectstack/core': minor |
| 4 | +'@objectstack/runtime': minor |
| 5 | +--- |
| 6 | + |
| 7 | +feat(spec)!: the canon for "the version of a package or plugin" is SemVer 2.0.0 — nine carriers, one grammar |
| 8 | + |
| 9 | +Clause-②: yes (narrowing) |
| 10 | + |
| 11 | +<!-- adr-0087: registered manifest-version-semver-2-0-0, plugin-version-semver-2-0-0, package-version-row-semver-2-0-0, package-manifest-version-grammar-enforced --> |
| 12 | + |
| 13 | +**BREAKING** — four published accept sets converge on one, and the fringe each |
| 14 | +of them carried outside SemVer 2.0.0 is refused. The widening half needs no |
| 15 | +action from anyone; the narrowing half is listed per carrier below, with its |
| 16 | +FROM → TO. |
| 17 | + |
| 18 | +One concept was judged by four different grammars across ten carriers in two |
| 19 | +repositories, and the strictest refused `2.0.0-beta.1` — the exact string a |
| 20 | +sibling declaration documented as an example of itself. The disagreement was |
| 21 | +observable between doors on the same resource, not merely between schema files: |
| 22 | +`os plugin build` refused a prerelease the publish door accepted, the Studio |
| 23 | +form refused it twice over, the `PATCH` door answered `400`, and the install |
| 24 | +door parsed nothing at all. An earlier change collapsed the eight regex literals |
| 25 | +onto three exported constants, which removed the drift but not the disagreement. |
| 26 | + |
| 27 | +`@objectstack/spec/kernel` now exports ONE grammar — |
| 28 | +`SEMVER_2_0_0_VERSION_PATTERN`, semver.org's own published expression — and |
| 29 | +every carrier references it. |
| 30 | + |
| 31 | +## What every author gains, with no edit |
| 32 | + |
| 33 | +Prerelease and build suffixes are accepted on the five carriers that demanded a |
| 34 | +bare three-segment core, so `2.0.0-beta.1`, `17.0.0-rc.5`, `1.0.0+20230101` and |
| 35 | +`1.0.0-rc.1+exp.sha.5114f85` now pass a key that refused all of them. Identifiers |
| 36 | +are case-preserving everywhere, as the standard requires. This repository cuts |
| 37 | +prereleases of its own packages while the key describing a package could not |
| 38 | +express one; that ends here. |
| 39 | + |
| 40 | +``` |
| 41 | +FROM ManifestSchema.parse({ id: 'com.acme.crm', version: '2.0.0-beta.1', … }) |
| 42 | + -> throws // and `os plugin build` exits 1 |
| 43 | +
|
| 44 | +TO ManifestSchema.parse({ id: 'com.acme.crm', version: '2.0.0-beta.1', … }) |
| 45 | + -> parses |
| 46 | +``` |
| 47 | + |
| 48 | +## What stops being accepted, per carrier |
| 49 | + |
| 50 | +Eight strings, all of them forms SemVer 2.0.0 forbids and none of them a valid |
| 51 | +prerelease. What they have in common is that no precedence order exists for any |
| 52 | +of them — `dependency-resolver.ts` can place none in an order — so a package |
| 53 | +versioned this way could be published and never compared against its own |
| 54 | +successor. |
| 55 | + |
| 56 | +``` |
| 57 | +FROM version: '01.1.1' TO version: '1.1.1' // §2 no leading zero in |
| 58 | +FROM version: '1.01.1' TO version: '1.1.1' // a numeric identifier |
| 59 | +FROM version: '1.1.01' TO version: '1.1.1' |
| 60 | +FROM version: '1.0.0-0123' TO version: '1.0.0-123' // §9 no leading zero in a |
| 61 | + // numeric prerelease id |
| 62 | +FROM version: '1.0.0-alpha..1' TO version: '1.0.0-alpha.1' // §9 no empty |
| 63 | +FROM version: '1.0.0-alpha..' TO version: '1.0.0-alpha' // identifier |
| 64 | +FROM version: '1.0.0-.' TO version: '1.0.0' |
| 65 | +FROM version: '1.0.0+.' TO version: '1.0.0' // §10 no empty build id |
| 66 | +``` |
| 67 | + |
| 68 | +⛔ Each repair above is one defensible reading and not the only one, which is |
| 69 | +why they ship as ADR-0087 D3 semantic TODOs rather than as mechanical D2 |
| 70 | +conversions: a version is how a release is addressed, so rewriting one |
| 71 | +re-points whatever already resolved the old string. Run |
| 72 | +`objectstack migrate meta --from <N>` for the per-site list. |
| 73 | + |
| 74 | +Per carrier: |
| 75 | + |
| 76 | +- `ManifestSchema.version` and its three sibling declarations |
| 77 | + (`MetadataPluginManifestSchema`, `PluginRegistryEntrySchema`, |
| 78 | + `PluginMetadataSchema`), plus the `PATCH /api/v1/packages/:id` door: gain the |
| 79 | + whole prerelease and build space; lose a leading zero in the numeric core. |
| 80 | +- `PluginSchema.version` and the plugin boot path in `@objectstack/core`: lose |
| 81 | + those eight and **nothing else**. ⭐ Every valid prerelease and build form the |
| 82 | + loader accepts today it still accepts, which is what keeps the widen-never- |
| 83 | + narrow ruling on that path honoured rather than reversed; both halves of that |
| 84 | + bound are pinned in `plugin.test.ts` and `plugin-loader.test.ts`. |
| 85 | +- `PackageVersionSchema.version`: gains case-preserving identifiers |
| 86 | + (`1.0.0-Beta.1`, `1.0.0+Build.5`), which the boot path has always accepted and |
| 87 | + this key alone refused; loses the same eight. |
| 88 | +- `PackageManifestSchema.version`: was a bare `z.string()` constraining nothing, |
| 89 | + so it is the one carrier where the grammar is entirely new. `latest`, |
| 90 | + `v1.0.0`, `1.0`, the empty string and `2.0.0-beta.1extra!` were accepted and |
| 91 | + frozen into a published manifest snapshot; each is refused now. A dist-tag |
| 92 | + becomes the version it pointed at, a `v`-prefix drops, a two-segment string |
| 93 | + gains its patch. |
| 94 | + |
| 95 | +## The prose moved with the grammar |
| 96 | + |
| 97 | +Every `.describe()` names SemVer 2.0.0 and the nine generated reference-doc rows |
| 98 | +follow; the `PATCH` door's refusal says so; `manifest.test.ts`'s |
| 99 | +「should enforce semantic versioning」 case stops listing `1.0.0-beta` among the |
| 100 | +invalid versions. `PluginLoader.isSemverShapedVersion` becomes `isSemverVersion` |
| 101 | +— a predicate named for a standard it does not implement gets misused by the |
| 102 | +next caller whatever its docblock says, and the name is true now. |
| 103 | + |
| 104 | +Three exported constants are retired, each replaced by the one canon: |
| 105 | + |
| 106 | +``` |
| 107 | +FROM import { MAJOR_MINOR_PATCH_VERSION_PATTERN } from '@objectstack/spec/kernel' |
| 108 | +FROM import { SEMVER_SHAPED_VERSION_PATTERN } from '@objectstack/spec/kernel' |
| 109 | +FROM import { SEMVER_SHAPED_LOWERCASE_VERSION_PATTERN } from '@objectstack/spec/kernel' |
| 110 | +TO import { SEMVER_2_0_0_VERSION_PATTERN } from '@objectstack/spec/kernel' |
| 111 | +``` |
| 112 | + |
| 113 | +⛔ They are not interchangeable with what they replaced — each named an accept |
| 114 | +set that no longer exists, which is why they are retired rather than aliased. A |
| 115 | +consumer that referenced one to REPRODUCE a verdict gets the canon's verdict |
| 116 | +now; one that referenced it to match a foreign grammar owns that grammar itself. |
| 117 | + |
| 118 | +The accept set is pinned witness by witness in `version-grammar.test.ts`: move a |
| 119 | +cell there and you have moved a published accept set on nine carriers at once, |
| 120 | +in one visible edit. |
0 commit comments