Software types
The software types are the values tools and build systems pass around: version numbers and ranges, git refs, package and container names, licences, trace ids, file names and paths. Each follows its standard, as the web types do.
Here a shop's plugin registry reads the version a release form names, offers a release only to the shops whose range it fits, and tells a newer release from an older one by SemVer's rules, not by its text:
type FormText = Text {}type YesNo = Bool {}type NotAVersion = Failure { kind: FailureKind.invalid_value}/// The version a plugin's release form names.function plugin_version(raw: FormText) fails NotAVersion -> SemanticVersion { var typed_version: SemanticVersion = attempt text.parse(raw, as: SemanticVersion) { malformed: NotAVersion } return typed_version}/// Whether a plugin's release fits the range a shop asks for.function fits_shop(plugin_release: SemanticVersion, shop_wants: VersionRange) -> YesNo { return YesNo(semver.satisfies(plugin_release, shop_wants))}/// Whether an offered release is newer than the one installed.function newer_than(installed_release: SemanticVersion, offered_release: SemanticVersion) -> YesNo { return YesNo(offered_release > installed_release)}/// Whether a release is offered to every shop: it has no pre-release.function generally_available(plugin_release: SemanticVersion) -> YesNo { return YesNo(plugin_release.pre_release == none)}/// A release's version as its tag writes it.function release_tag(plugin_release: SemanticVersion) -> FormText { return FormText(text.from(plugin_release))}
test "versions follow SemVer's precedence, not their text" { var nine: SemanticVersion = attempt plugin_version("1.9.0") var ten: SemanticVersion = attempt plugin_version("1.10.0") var release_candidate: SemanticVersion = attempt plugin_version("1.10.0-rc.1") assert newer_than(nine, ten) assert newer_than(release_candidate, ten) assert not generally_available(release_candidate) assert release_tag(release_candidate) == "1.10.0-rc.1" assert plugin_version("v1.10.0") fails NotAVersion}test "a range takes a pre-release only where it names one" { var patch_release: SemanticVersion = attempt plugin_version("1.4.2") var beta_release: SemanticVersion = attempt plugin_version("2.0.0-beta.1") assert fits_shop(patch_release, "^1.2.3") assert not fits_shop(patch_release, "~1.2.3") assert not fits_shop(beta_release, "^1.2.3") assert not fits_shop(beta_release, ">=1.0.0") assert fits_shop(beta_release, ">=2.0.0-beta.0 <3.0.0")}
Which standard each type follows
| Type | Standard |
|---|---|
SemanticVersion |
SemVer 2.0.0, 1.2.3-alpha.1+build.5 |
VersionRange |
npm's range grammar, written one way: ^1.2.3, >=1.0.0 <2.0.0, 1.2.x || >=2.5.0-beta.1 |
GitRef |
what git check-ref-format accepts |
PackageUrl |
ECMA-427's canonical pkg: URL |
Cve |
the CVE programme's id |
SpdxLicence |
an SPDX expression over the SPDX licence list |
TraceId, SpanId, TraceParent |
W3C Trace Context |
LogLevel |
OpenTelemetry's six severities |
FileName, FilePath |
names and paths every common file system takes, written with / |
RegularExpression |
a pattern that pattern:'s linear-time engine takes |
ContainerImage |
the OCI distribution reference, with its registry and a tag or a digest |
Digest |
the OCI image specification |
Ulid |
the ULID specification's 26 characters |
IdempotencyKey |
what the Idempotency-Key header carries |
Doi |
the DOI Handbook's name |
None of them is personal data. type.standard_exists matches none of
them, so your own { major, minor, patch } type stays your own for now:
adding a shape later would report every such type at once.
Versions
A SemanticVersion is an object of major, minor and patch, each a
whole number from 0 up, and pre_release and build, each text or
none. So code reads its parts: release.pre_release == none.
Its canonical text has no v before it, and a number too large for an
Int is malformed. It is sent as that text and shown as its text. It is
stored as a column per part: bigint for the numbers, and text or
NULL for the rest.
< is SemVer 2.0.0's precedence (§11), so 1.10.0 comes after 1.9.0
and every pre-release comes before its release:
- the numbers compare numerically;
- a pre-release is below its release;
- pre-release identifiers compare numerically when they are numbers and in ASCII order when they aren't, numbers first.
Build metadata is ignored, as §10 says, so neither of 1.0.0+a and
1.0.0+b is below the other. They still aren't equal, because ==
compares every part. Sorting and a set's order go by precedence, then by
build metadata as text, so they stay total and agree with ==.
A query can't order by a version, because its columns don't sort by precedence.
Version ranges
A VersionRange is the range a package asks for, in npm's range grammar
(node-semver's), because SemVer defines none. It is one or more sets
joined by ||. Each set is made of comparisons (<, <=, >, >=),
~, ^, partial versions (1.2, 1.2.x) and hyphen ranges
(1.0.0 - 1.4.2), with * for any version.
A range is read in one spelling only:
- no
vor=before a version; - no space after an operator, and one space between comparisons;
- the wildcard
xinside a version, and*only on its own; - no build metadata, which a range can't use.
Each form means what node-semver expands it to: ^0.2.3 is
>=0.2.3 <0.3.0-0. A range is held as its text, so two spellings of one
set, such as 1.2 and 1.2.x, are different values.
semver.satisfies(version, range) says whether a version is inside a
range, and never fails. It keeps node-semver's rule for pre-releases,
which Cargo shares: a version with a pre-release is inside a range only
where a comparison in the same set names a pre-release of the same major,
minor and patch. So ^1.2.3 never takes 2.0.0-beta.1, and * takes no
pre-release.
Types held as text
The other software types are held as text:
- A
GitRefis any namegit check-ref-format --allow-onelevelaccepts: a branch (main), a tag, a full ref (refs/heads/main) or a commit's full hash. It isn't normalised with--normalize, so a leading/or a//is refused. Its length isn't limited, as git's isn't. - A
PackageUrlis ECMA-427's canonical form:pkg:with no//, a lower-case type, and each component percent-encoded only where §5.4 says, in upper-case hex, with none ofA-Z a-z 0-9 . - _ ~ :encoded. Qualifier keys are lower case, sorted, and each appears once with a value. A subpath has no.,..or empty segment. A registered type's own rules, such as npm's lower-case names or PyPI's normalisation, aren't checked; checking them would refuse more, never accept more. - A
CveisCVE-, a year from 1999, a-and a sequence number of four digits, or of five to nineteen digits with no leading zero, in upper case. That is the CVE programme's 2014 syntax, with its JSON schema's limit of nineteen digits. - A
SpdxLicenceis an SPDX 2.3 expression (Annex D) over the SPDX License List 3.29.0, bundled by Embark'sspdxcrate 0.13.6 (CC0 data, Apache-2.0 code). It holds each identifier as the list spells it, deprecated ones included since they still name a licence;+;WITHan exception on the list;AND,ORand parentheses; andLicenseRef-andDocumentRef-references. Operators are upper case, words are one space apart, and parentheses have no space inside.NONEandNOASSERTIONare values a document uses, not expressions, and are refused. - A
TraceIdand aSpanIdare W3C Trace Context's ids, in lower-case hexadecimal, never all zeros. - A
TraceParent, the header that carries a trace from one service to the next, is text, asCidris, because no operation reads its parts. Only version00is read: it is the one version defined, and its length is fixed. Its flags are any two hex digits. - A
FileNameis a name every common file system takes:- 1 to 255 bytes of UTF-8;
- no
/,\, NUL or other control character, and none of Windows'< > : " | ? *; - not
.or.., and no space or dot at the end, which Windows drops; - not a Windows device name (
CON,PRN,AUX,NUL,COM0toCOM9,LPT0toLPT9, with¹²³), in any case and with any extension, sonul.txtis refused.
- A
FilePathis written with/, as POSIX systems and URLs write one, so a Windows path such asC:\Usersis refused. It is/alone, or names joined by/, absolute after a leading/, each aFileName. It is in its shortest form: no.segment,..only at the start of a relative path, no empty segment, and no/at the end. It is at most 4096 bytes, Linux'sPATH_MAX. It says nothing about where files are kept: the file stores never make a key from a name. - A
RegularExpressionis a pattern that a type'spattern:takes (Constraints), compiled byjadpo_std::compile_patternwith theregexcrate. So backreferences and lookaround are refused, as they are there. It is matched against the whole text, and it isn't empty. - A
ContainerImageis an OCI distribution reference, written in full: a registry first (a domain in lower case with a.or a port, an IP address, orlocalhost), then the repository's components in lower case, then a tag, a digest or both. Songinx:1.27, Docker Hub's short form ofdocker.io/library/nginx:1.27, is refused, and so is a reference with neither a tag nor a digest (an impliedlatest). Each image has one text. - A
Digestfollows the OCI image specification's grammar. Its registered algorithms are held to their own forms:sha256andblake3are 64 lower-case hex digits,sha512128. An unregistered algorithm passes when it meets the grammar, as the specification asks. - A
Ulidis in upper case only, its first character at most7. It isn't ordered, as no type held as text is. - An
IdempotencyKey, the key a client sends so that a repeated request isn't carried out twice, is 1 to 255 printable ASCII characters with no"or\and no space at either end. That lets a structured header field carry it unescaped. The draft standard recommends a UUID but doesn't require one. - A
Doiis10., a registrant code of four to nine digits with any subdivisions,/, and a suffix of printable characters, its ASCII letters in lower case because the DOI Handbook compares them ignoring case.doi:and resolver URLs are refused.
Log levels
A LogLevel is an enum of OpenTelemetry's six severities: trace,
debug, info, warn, error and fatal. All six are there, so a
later addition doesn't break a match. Its codes are their short names,
such as INFO, read ignoring case, and it is stored by its code, as
HttpMethod is.
A LogLevel isn't ordered, because no enum is yet. So when
Telemetry keeps "logs from
LogLevel.warn", the runtime reads that order itself.
Markdown for agents: /docs/software-types.md.Built from Jadpo main at a707efb.