Skip to content

Release candidate - #181

Draft
jwrosewell wants to merge 99 commits into
mainfrom
rc
Draft

jwrosewell wants to merge 99 commits into
mainfrom
rc

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

Releases what has collected on rc since the last release, #134, which merged on 11 November 2026 at 15:07 UTC. The release head is dbaf099c, and every link to the text below is fixed at that commit.

This release comes five days after #134, inside the week, by James Rosewell's decision of 13 November 2026 that every repository releases on Monday 16 November.

One rule on rc is open, so this does not merge as it stands. rc holds the Package elements, a version for every 51Degrees package in each usage sharing message (#156, usage-sharing-element.md lines 208 to 216). James Rosewell is looking at that rule again because of the size the elements add to every message. #180 takes the elements back out of rc and waits for his word. This release merges once #180 has merged, or once he has said the rule stands as #156 wrote it.

The packages released on 16 November link the specification on main from their code comments and documentation, and follow the rules below, so this merges into main before any language's release.

Conformance with the specification

These are the rules every implementation now has to meet. Each language's release of 16 November says which it meets.

Usage sharing

  1. No cookie is shared but a 51D_ one, even when all Evidence is shared. "It MUST also return false, whatever the configuration and even when all Evidence is shared, for any key whose prefix is cookie. and whose field does not start with 51D_." (usage-sharing-element.md lines 65 to 69, #155). The session cookie follows, as it "MUST NOT be shared unless its name starts with 51D_, as no other cookie is" (lines 98 and 99).
  2. Every query string and form parameter is shared by default. "Share all query string parameters is on by default, so every query string and HTTP form parameter is shared unless a user turns it off." (lines 80 and 81, #155).
  3. The session id and sequence never enter the repeat comparison, and are always in the record. "The session id and sequence, query.session-id and query.sequence, MUST NOT be included in the generation of this hash code, whatever the other options say, and MUST still be written to the record." Both keys are matched ignoring letter case (lines 318 to 332, #146).
  4. The Package elements, the open rule above (#156, taken back out by #180).

The Sequence Element

  1. A sequence counts only as an integer or as digits. "A query.sequence value counts only when it is held in one of the language's integer types, or is a string made only of the digits 0 to 9 with no sign or spaces, and only from 0 up to 2,147,483,646", and "A floating-point value is not present even when it has no fractional part, so 2.0 is absent where 2 is not." (sequence-element.md lines 40 to 47, #158).

Data updates

  1. The file system watcher runs on its own setting and needs no License Key. "Automatic update from File (file system watching) does not depend on autoUpdate. The file system watcher runs on its own setting, whether autoUpdate is enabled or disabled, because watching a file downloads nothing." and "The file system watcher needs no License Key." (data-updates.md lines 99 to 109, #143). The text on main made autoUpdate the switch for the watcher too, so a deployment with its own data file and no License Key could not have the Engine refresh when a new file was put in place.
  2. A download cut short is the first error row. The row "Connecting to the data update URL failed" now reads "Connecting to the data update URL failed, or its response ended before the whole file arrived", and "No response" is "The request to the data update URL ended with no response and no error". Messages, levels and statuses are unchanged (data-updates.md lines 500 and 501, #145).

On-premise Engines

  1. Reading a Property by position is a SHOULD, with one exception that needs proof. "Every read SHOULD use the position." and "An implementation MAY read by name where its own tests prove that a read by name is faster than a read by position, as the fastest approach is the one used. The proof MUST be a measurement a test or a continuous integration run takes and records" (device-detection-on-premise.md lines 205 to 221, ip-intelligence-on-premise.md lines 411 to 427, #141). The text on main said every read MUST use the position. Both pages list the reads that go by name in Node.js, .NET and PHP, each with its change and its run.

Lazy loading

  1. A timeout given as a number is in milliseconds. "Where a language takes the timeout as a number, the number MUST be in milliseconds. Where a language has a type for a length of time, such as TimeSpan in .NET or time.Duration in Go, it MAY take that type instead" (lazy-loading.md lines 23 to 28, #147). The page now names Go beside .NET and Java as implementing lazy loading.

Examples

  1. A cloud example run with no Resource Key ends cleanly. "A cloud example run with no Resource Key MUST end without an error, name the environment variable to set, give a link to where a Resource Key is created", "and exit successfully, so that its test runs it with no arguments or settings". It "applies to every example that uses the cloud service, whatever its engines" (device-detection-specification/required-examples.md lines 140 to 150, ip-intelligence-specification/required-examples.md lines 154 to 158, #157).
  2. The getting started consoles meet their requirement between them. "This is met when the console examples between them show both the fluent builder and an options file. Four separate examples, one for each of Cloud and On-Prem with each way of configuring, are not required." (device-detection-specification/required-examples.md lines 241 to 243, #159).

Before you upgrade

A specification has no package and no version to upgrade. These are the changes a user of a package meets once that package follows the rules above, and each language's release says whether it does.

  1. Usage sharing shares every query string and form parameter by default (rule 2). To share only the 51D_ ones and those in the included list, as before, turn share all query string parameters off.
  2. Share all Evidence no longer shares a site's own cookies (rule 1). Only 51D_ cookies are shared, whatever the settings. Track session now works the same whether or not all Evidence is shared.
  3. A floating-point query.sequence is ignored (rule 5). An application that adds the sequence to Evidence itself gives it as an integer or as digits.
  4. The file system watcher runs with autoUpdate off and with no License Key (rule 6). This reverses the earlier text, under which turning autoUpdate off also stopped the watcher. An application that wants no watcher turns the watcher's own setting off.
  5. A lazy loading timeout given as a number is read as milliseconds (rule 9).

New features

None. A specification gains rules, which are listed above.

Performance and test coverage

  • The two On-premise Engine pages record the measurement behind each read by name, for Node.js, .NET and PHP, with the run that took it (rule 8, #141).
  • This repository has no tests of its own. Each language tests the rules above in its own release.

Other changes to the text

  • Why the On-premise Engines have no results cache. Both On-premise Engine pages now give the reason, being the coordination of a result's lifetime and thread safety with the C code that owns the memory it reads, and say that a caller who needs a cache can keep the values it reads in the Pipeline's own caches (#34).
  • The derived property example no longer names the Properties the HumanConfidence script reads, and lists its four values, High, Medium, Low and Unknown (#147).

CI and housekeeping

  • main was merged back into rc after the last release (#140).

Not in this release

  • #142, the configuration page's new address in the required examples page and the two missing Property messages, is still an open draft into rc. It merges with the same change in the other repositories, and #179 is about the same two messages.

Issues this closes

Closes #144.

The other 27 open issues stay open.

Build

The specifications have no build. Every check ran on the release head dbaf099 and passed.

  • UTM link lint, run 43756102354.
  • Only rc into main, run 43756102690.
  • The Nightly Pipeline, dispatched on rc with dryrun set, run 43756102753, green in Get Pull Requests, Configure, Build and Test on Ubuntu (which runs the link lint against the merged result) and Complete. Compare Performance is skipped, as the workflow sets skip-compare-performance.

If #180 merges into rc, the head moves and these checks run again on the new head before this merges.

This repository publishes no package and tags no version, so there is no Publish dry run, no version to move and no timing against main.

justadreamer and others added 30 commits November 3, 2026 15:32
The rust repository is now 51degrees-rust (51Degrees/51degrees-rust#206).
The list of packages that implement the 51Did specification links
the new name.
Keeps the 51degrees-java link from #64 next to it.
…ies (#71)

Every link to pipeline-dotnet, device-detection-dotnet,
device-detection-dotnet-examples, ip-intelligence-dotnet, location-dotnet
and caching-dotnet now points at the same file or folder in
51degrees-dotnet, and the 51Did package list names 51degrees-dotnet,
51degrees-python and 51degrees-php. Line numbers that had drifted from
the code their links name point at that code again.

The link to an issue in pipeline-dotnet stays, as issues stayed there.
Neither the .NET nor the Java packages have the option, so the
Configuration options section of the JSON builder now reads None, as the
Sequence element's does.
A value converted from Azimuth or Declination is given rounded to 3
decimal places as a number, a string or a WKT point, because the
locations are rough and more places only lengthen the strings and WKT
shapes. A package may offer a setting for a different number, with 3 as
the default.
A package ignores white space before and after the text and refuses any
other character outside the alphabet, white space inside included, as RFC
4648 section 3.3 requires when the referring specification says nothing
else.
* Refuse a 51Did base64 text that mixes the two alphabets

A text uses one alphabet, so a package refuses one holding + or / beside
- or _. RFC 4648 section 5 says the URL-safe encoding should not be
regarded as the same as base64.

* Rest the one alphabet rule on what base64 libraries do

Base64 libraries write one alphabet or the other and never a mix, and
most of their decoders refuse a mixed text. The paragraph now says so
first, keeps RFC 4648 section 5 as agreement, and has a package that
maps one alphabet onto the other check for a mix before mapping.
The Evidence and thread safety pages said Evidence never changes once
created, while the Sequence Element page said the reference
implementations write the session id and sequence back to Evidence.
The .NET and Java implementations agree that a Flow Element adds values
through the Flow Data during processing, replacing any value held under
the same key, with no check against the accepted Evidence. The pages
now say what they do, and the Sequence Element adds both values to
Evidence as part of its process rather than as a deviation.
)

The Translation Engine page listed query.translation then
header.accept-language. The .NET and Java implementations both read
query.accept-language between the two, in that order of precedence, so
the page now lists all three as they do.
…xt (#93)

The pipeline configuration page says a list of strings in a
configuration file is a comma-separated string, which is also the only
form .NET reads. The Derived Property Element example gave Scripts and
ScriptFiles as JSON arrays, and the Translation Engine example gave
Sources as JSON arrays, one with a trailing comma that made the example
invalid JSON. Each is now a comma-separated string, with a sentence
saying so beside each example.
)

The accepted evidence section now says that the prefix of a key is
everything before its first dot, and that id.email, id.salt,
request-query and request-path are never shared, in any mode, as the .NET
packages already do. It names the HTTP header that carries cookies as
Cookie, which RFC 6265 section 5.4 defines, where it said "cookies", which
matches no header, so the raw header no longer undoes the cookie rule. It
also gives the session. rule every package already applies.
No package reads Sources or Translations from a configuration file. A
translation is a pair of values and AddSource has a two value overload,
so the configuration example now sets only the single value options and
the page says sources and translations are added in code, or by a
builder made for one translation. The example also spells
MissingTranslationBehavior as the builder method does.
The row said the native default applies and then that the option is not
enabled by default. The C library's default configuration sets it to
true, and the .NET builder leaves it at that native default, so the row
and the UserAgents match metric now say it is on by default.
…owing it (#96)

* Hold the Hash engine's concurrency at what its caches allow

The native code refuses a cache whose parts hold fewer entries than
there are parts, so a profile with a cache read from the file allows at
most the whole square root of its smallest cache's capacity. The
Concurrency row now says the default is held at that number and a
larger named value is refused when the Engine is built.

* Say what Concurrency does in the native code, for both engines

Concurrency is the number of file handles in the pool as well as the
number of parts each cache is split into. An operation that finds no free
handle fails with INSUFFICIENT_HANDLES, and a cache part smaller than
the number of parts stops the Engine building with CORRUPT_DATA, so a
profile that reads through a cache serves at most the whole square root
of its smallest cache's capacity at once. The default is the native 10,
not the processor count. The device detection and IP Intelligence rows
now say this, replacing the earlier wording on this branch.

* Wait for a free file handle rather than fail

James Rosewell decided the file handle pool waits for a handle to be
released instead of failing with INSUFFICIENT_HANDLES. Both Concurrency
rows now say an operation waits, so a low Concurrency slows operations
that read from the file rather than failing them.

* Split caches so any Concurrency builds, defaulting to the processor count

James Rosewell decided how Concurrency works in the native code.
Concurrency is the expected number of operations at once and defaults to
the processor count. It is the number of file handles, and an operation
waits for a free one. Each cache is split into Concurrency parts, or
fewer so that every part holds at least Concurrency items, and a cache
smaller than Concurrency grows to fit, so no value stops the Engine
building.

* Cap the file handles at 64

A process can only have so many files open, 512 through fopen by default
on Windows, shared by every engine. James Rosewell chose a fixed maximum,
so the pool holds Concurrency handles up to 64 and operations beyond that
wait for a free one.
The Usage Sharing Element accepted every header except Cookie, which made
the Pipeline accept every header, so the ETag and Vary of the client-side
endpoints covered If-None-Match and every other header. It now accepts no
Evidence and shares what it finds in the Flow Data and in the web request,
which the web integration adds as server.http-request.
Thread safety asks only for Evidence that is safe to read from several threads, and the .NET Evidence is a plain dictionary written with no lock, so a member of a parallel group cannot add Evidence while the other members read it.
…configuration file (#100)

* Let a package set Translation Engine sources and translations from a configuration file

The page said a configuration file cannot set sources or translations, on
the understanding that no package read them from one. Go, Node and Python
all do in released versions, so removing the route would break customer
configuration files. A package MAY now offer it, choosing the form for
the two pairs, a translation and a source given as file contents, using
the names it uses in code and documenting the form with an example.

* Leave the file form of sources and translations to pipeline configuration

The paragraph let each package choose how sources and translations are
written in a configuration file. That works against pipeline configuration,
which asks for configuration files that are consistent between languages and
for the same names in the file as in code. The paragraph now keeps the MAY,
what the two pairs hold, and the route in code for a package that does not
offer the MAY, so the existing pipeline configuration rules govern the form.
…n-premise engines (#102)

* Say that PHP always uses MaxPerformance, for both on-premise engines

PHP serves requests from worker processes forked from the process that
built the Engine, and a fork copies the open file handles with one shared
read position, so a profile that reads from the file corrupts reads.
PHP device detection already enforces MaxPerformance for this reason and
PHP IP Intelligence is moving to the same rule. The Performance Profile
row on both on-premise pages now gives Balanced as the default except in
PHP, with a footnote saying why.

* Limit PHP's MaxPerformance rule to the Engine built at start-up

Only an Engine built before PHP forks its workers shares file handles
between processes. An Engine built after the fork, or in a process that
never forks, opens its own handles, so it may use any profile.

* Have the PHP footnote's last sentence cover any other Engine

The footnote's first sentence says the Engine built when PHP starts always
uses MaxPerformance. Its last sentence let an Engine in a process that never
forks use any profile, and a start-up Engine in the PHP command line, which
never forks, fitted both sentences. The last sentence now covers any other
Engine, such as one built after the fork or in a process that never forks,
so the start-up Engine always uses MaxPerformance.
…sponse (#53)

* Keep the ETag the same in every process, and send none on a failed response

The ETag of the client-side endpoints is a hash of the Evidence, and a browser
revalidates with the one it kept. The web integration feature now says the
same Evidence gives the same ETag in every process running the same build, so
a restarted server or another one behind a load balancer can match it, and
that a response to a request whose processing recorded errors has no-cache
and no ETag, so a browser does not keep the faulty response once processing
works again.

* Keep a failed client-side response out of shared caches

A response to a request whose processing recorded errors carried bare
no-cache, which lets a shared cache store a response made for one visitor
(RFC 9111 sections 3 and 5.2.2.7), where every other response from the
JSON and JavaScript endpoints is private. Make it private, no-cache.

* Answer a matching If-None-Match with 304 or 412 as RFC 9110 says

RFC 9110 section 13.1.2 gives a 304 to a GET or HEAD request whose
If-None-Match matches and a 412 to any other method, so the rule that a
matching request is answered while processing is failing now says the
same, and agrees with the 412 for a POST to the JSON endpoint. Say that
a failed response differs from the table in two rows, link the Flow
Data errors collection, and name what the paragraph on stale values
is about.

* Say a matching request gets the same status code when processing records errors

The 304 sent while processing records errors carries private, no-cache and
no ETag, so the whole answer is not the same as at other times, only its
status code. When processing throws there is no 304 at all, so the rule now
names the case where processing records errors, and says the header matches
the ETag rather than "it".
* Give each missing Property message as the .NET packages write it

The missing Properties table differed from the messages in the .NET
packages' Messages.resx, and had no row for an unknown reason or for a
failed request to the remote server. Bring each message into line with
.NET, name each reason as MissingPropertyReason does, and say how the
reason is decided, as MissingPropertyService and
MissingPropertyServiceCloud do.

* Apply the typed accessor step only to a Property the metadata does not list

A Property the metadata listed, with the Engine's own data file tier and as
available, reached step 3.3 and could be given an upgrade message naming
the tier the customer already has. The .NET packages cannot produce that
case, because their on-premise Engines find no typed accessor and a Cloud
Engine with a listed Property stops at step 3.2. Also say that the type name
in the upgrade message differs between languages.

* Give each missing Property one reason that does not mislead

Decided at review on 5 November 2026. The typed accessor step applies to
On-premise Engines only and counts inherited accessors. A Cloud Engine
without its Property metadata gives CloudRequestFailed when its request for
the property list failed, and Unknown otherwise, and one that read its
Resource Key's product list and found no entry for its element has loaded
metadata with no Properties. The upgrade message leaves out the sentence
naming the data tiers when they are not known. Names are compared ignoring
case, as the Element Data compares them when a value is read.

* Say that .NET is to change to match the missing Property rules

The page called the .NET packages the reference implementation, but
Messages.resx has no text for the second row, and .NET differs from
the section in the five places decided at review. Say which rows .NET's
text gives and what .NET is to change. Make row 6's parameters easier
to read.
* Check the callback URL before the JavaScript builder writes it

The protocol and host of the callback URL can come from Evidence, and the
template writes the URL inside a single-quoted JavaScript string. Step 6
now points to a new Callback URL section with the rules every
implementation applies to the scheme, the host, the port, letter case and
percent-encoding. A URL that fails them is left out and logged once.

* Allow only ASCII in a callback URL's host name

A Host header's syntax allows only ASCII, and RFC 3986 asks for an
internationalized name in its IDNA form, so a host name label holds only
ASCII letters, digits, hyphens and underscores, and a configured
internationalized host is given as A-labels. This removes the need for a
Unicode letter test, whose meaning differs between languages. Step 6 now
says what one slash between host and endpoint means, and that a part that
is empty or only whitespace is not set.

* Allow only http and https as a callback URL's protocol

The script uses the callback URL only with fetch and XMLHttpRequest, and
the Fetch Standard's scheme fetch gives a network error for every scheme
but about, blob, data, file, http and https, of which only http and https
reach a server. A callback with any other protocol, such as ftp, gave a
script whose update always failed, with nothing logged on the server.
Refuse it as rule 6 says, and refuse a configured protocol that is neither
when the element is built, as the .NET and Rust builders already do.

* Compare a callback URL's protocol ignoring ASCII case only

Comparing ignoring case in general lets a letter outside ASCII match one
inside it. Java's String.equalsIgnoreCase treats the long s (U+017F) as s,
so httpſ would pass as https and then fail in the browser. Compare
ignoring ASCII case only, as the Rust check's eq_ignore_ascii_case does.

* Refuse a zone identifier, user information, and ? or # in the callback host

RFC 9844 obsoletes RFC 6874, so RFC 3986's IPv6 literal has no zone
identifier again, and the URL Standard's IPv6 parser fails on a %. The
Fetch Standard's Request constructor throws a TypeError for a URL with
credentials, and a ? or # in the host value put the endpoint into the
query or fragment. Refuse all three.

Say that every host the rules accept is one RFC 3986 allows. Define white
space as the Unicode White_Space characters. Say that a configured
protocol of only white space is refused, while an empty one means none
is configured. Log a failed URL the first time for each instance of the
element.

* Refuse a callback host that ends in a number unless it is an IPv4 address

The URL Standard's host parser reads a name whose last label, ignoring a
final dot, is all digits or 0x followed by hex digits as an IPv4 address,
and fails if it is not one, so a browser cannot parse a callback holding
example.123 or 1.2.3.999. Such a name must now be an IPv4 address in RFC
3986's IPv4address form. That also refuses 127.1, 0x7f.0.0.1, 01.2.3.4 and
1.2.3.4., which the URL Standard reads as addresses, so a callback host has
one written form.

Rule 3 now says why all user information is refused. fetch refuses
credentials, but XMLHttpRequest, which the script uses otherwise, accepts
them, and an empty user name is not credentials. Rule 6 now keeps the user
information out of the logged error, so a configured password is never
logged.

* Leave everything before the host value's last @ out of the logged error

Rule 6 left out of the log only what came before the authority's last @,
but a password can hold a / that is not encoded, so the configured host
user:pa/ss@example.com has the authority user:pa, which holds no @, and
the log carried the password. An authority ending at ? or # fails the
same way for user:pa?ss@example.com. The error now shows *** in place of
everything in the host value before its last @, which can leave part of
a refused host out of the log, as exa mple.com/a@b is logged as
https://***@b/51dpipeline/json.

Rule 4 said each part of an IPv4 address has no leading zero, which
could be read as refusing 0.0.0.0. It now says each dec-octet has no
zero before another digit, with examples.

* Refuse an @ anywhere in the callback host value

A password can hold a / that is not encoded, so the configured host
user:12/ss@example.com has the authority user:12, which holds no @ and
passed rules 4 and 5 as the host user and the port 12. Every script
served then carried the password and called back to the host user. Rule
3 now refuses an @ anywhere in the host value, so example.com/a@b is
refused too. Rule 6's example names rule 3 as one that refuses it.

Rule 4 said each dec-octet has no zero before another digit, which by
its words refuses 101. It now says a dec-octet starts with 0 only when
it is 0 alone. RFC 3986 section 7.4 is now cited only for 127.1,
0x7f.0.0.1 and 01.2.3.4, as RFC 3986 reads 1.2.3.4. as a registered
name.
* Define the Hashed Email match key exactly

The match key is the SHA-256 of the salt length as one byte, the salt,
then the lowercased address part of the email as UTF-8. The address part
is the addr-spec of an RFC 5322 mailbox, so a display name, angle brackets
and comments do not change the key.

* Say exactly which bytes the Hashed Email match key hashes

Existing keys depend on how the email is read and lowercased, which the
draft did not fix exactly. .NET's MailAddress refuses some obsolete
forms and three current ones, keeps the quote characters and
backslashes of a quoted local part, and puts the address in
Normalization Form C, and ToLowerInvariant uses the Unicode simple
lowercase mapping except for U+0130. Say each of these, define the
white space that is ignored, and add a worked example with a quoted
local part.

* Refuse a Hashed Email that is not a current-syntax RFC 5322 mailbox

The issuer must now refuse every email that is not a mailbox in RFC 5322
section 3's syntax without the obs- rules, such as user name@example.com, a
list of addresses or the obsolete john . smith@example.com, where before it
could accept one when it could read an address part from it. RFC 5322's
syntax is ASCII only, so an email holding a character outside ASCII is
refused too. The issuer may still refuse the three current forms that hold
a line break, a quoted-pair of a space or a tab, or a display name holding a
quoted-string and another word.

* Accept a Hashed Email mailbox that holds UTF-8, as RFC 6532 allows

A mailbox is now one in RFC 5322 section 3's current syntax as RFC 6532
section 3.2 extends it, where VCHAR, ctext, atext, qtext and dtext each
also allow any character outside ASCII written as UTF-8. So an email such
as Üser@Exämple.com is accepted again rather than refused, and a worked
example gives its bytes and Match Key, which do not depend on whether Ü
and ä are written as one character or as a letter and a combining mark.
The issuer may also refuse a domain literal holding a character outside
ASCII, and U+FFFE, as .NET's MailAddress refuses both.

* Remove white space around a Hashed Email before reading it as a mailbox

The page said white space around the email is ignored, but not when.
Since RFC 6532, U+00A0 and the other White_Space characters outside
ASCII are atext, so U+00A0 followed by user@example.com is itself a
mailbox whose address part keeps the U+00A0, and user@example.com
followed by U+000B is not a mailbox at all. Removing the white space
first gives user@example.com for both. The page now says the white
space is removed before the email is read as a mailbox, and that every
rule below applies to what remains. The first use of mailbox now says
it is the one defined below, which RFC 6532 extends.
…s sent (#131)

* Honor Requested Properties beside a Resource Key when a License Key is sent

James Rosewell decided on 10 November 2026 that with both keys the remote server uses the Resource Key's licence and the License Key given, and the response holds the Resource Key's Properties plus the Requested Properties the License Key grants. The live service already answers that way. A Resource Key with Requested Properties and no License Key is still refused, as the list is ignored.

* Say exactly what Requested Properties do beside a Resource Key with no License Key

The list is not simply ignored. A requested Property the Resource Key carries changes nothing, and one it lacks comes back null with the paid feature nullreason, so the list adds no data. Records the five measured cases, and why the server answers so: it merges the Resource Key's licence and Properties with the request's before checking entitlement.

* Accept Requested Properties with a Resource Key and no License Key

James Rosewell corrected the table on 10 November 2026. The requested Properties are added to those the Resource Key carries, one no licence grants is answered as null with the paid feature nullreason, and a name that is not a Property is not returned, so the combination is valid and no Engine refuses it.
…#132)

James Rosewell decided on 10 November 2026 that the keys are always shared. A Pipeline that authenticates holds them as fiftyone.resource-key and fiftyone.license-key, and the filter shares both whatever its configuration, because usage is counted against them.
…ies not returned section and the configuration table, and correct the reading of the 21 September measurement (#133)
…ue (#105)

* Say how a Device Detection Engine gives each data file property's value

Adds a Property values section to the on-premise Device Detection page.
It says where a data file Property's value comes from, what a List, a
string, a Boolean and a number give, and how JavascriptHardwareProfile
values are wrapped. It also says the match metric Properties come after
the data file Properties, in the order of their table.

Adds a paragraph to value-types.md saying how a typed accessor reads a
Boolean or number stored as a string.

Gives the cloud example response a string null reason for
javascripthardwareprofile, as json-builder.md asks.

* Number the Property value rules, say the Profile holds the values, and use the native no-value message

The Property values paragraph chained conditions with "Otherwise", each
referring to a different earlier one, and is now five numbered rules. The
value types text says the Profile, not the result, holds more than one
value. The cloud example's null reason is now the C library's message for a
Property no result holds a value for (hash.c, NO_RESULT_FOR_PROPERTY),
rather than wording that no implementation produces.

* Show the cloud example's null reason as the cloud returns it

No code path gives the message the example used. The live cloud service answers javascripthardwareprofile with null and javascripthardwareprofilenullreason with null for a phone and a desktop whose Profile holds no value, so the example keeps that.

* Limit the match metric order to the Property metadata

A JSON object is an unordered collection of name/value pairs (RFC 8259 section 1), and Element Data is a map in several languages, so the order of the table applies to the Engine's Property metadata, which every language keeps as a list.

* Show the null reason the C++ results give once they report a Profile holding no value

The C++ results gave no reason when the matched Profile holds no value for a Property, so the cloud answered null, which the JSON Builder page does not allow. The C++ fix reports NO_RESULT_FOR_PROPERTY with its message, so the example shows that message.
* Say what the endpoints answer to a matching If-None-Match

A GET or HEAD gets a 304 and any other method a 412, as RFC 9110 section 13.1.2 requires, and a 304 carries the headers a 200 would, as section 15.4.5 requires.

* State the 304 or 412 rule once, and give the 304 headers after it

rc now states the 304 or 412 rule, with the RFC 9110 section 13.1.2
citation, in the paragraph on a matching If-None-Match while processing
records errors. This branch's own statement of the same rule is removed,
and the earlier sentence that said the endpoint can just return a 304
now says only that the content is not sent again and that the status
code depends on the method, as described below.

The paragraph on the headers a 304 carries, from RFC 9110 section
15.4.5, now follows the rule it depends on.
…sing (#136)

* Say that the endpoints answer a matching If-None-Match without processing

* Make comparing If-None-Match before processing a MUST, as RFC 9110 requires

* Say a matching request would record errors, as it is not processed
…ameters (#84)

The .NET and Java JavaScript builders leave query.session-id and
query.sequence out of the parameters passed to the template, because the
template adds both to each callback itself. The specification said to take
every query. Evidence value.
…#82)

The .NET and Java JSON builders add their own Element Data to the Flow Data
before building the JSON, and leave it out with an exclusion list of
set-headers, cloud-response and json-builder. The specification's list named
the usage sharing element, which adds no Element Data, and did not name the
JSON builder itself.
* Log once when a derived property's source Element runs too late

The specification said nothing about the error the .NET packages log when
a request carries no Element Data at all under a key a script reads, which
happens when the Element supplying that key runs after the derived
property Element. It also said that nothing is shared between requests,
which rules out the flag .NET keeps so that the error is logged once per
script. Describe the error, and allow one flag per script, set once
without a lock.

* Name both causes of missing source Element Data, and add the one-time error to the late Element paragraph

A supplier that ran first but failed before adding its Element Data also leaves no Element Data under the key, because exception-handling.md has every Flow Element run after one fails. The paragraph on an Element placed too late now also says the Element logs the error once.
…es needs no decoder (#108)

* Say an Engine whose native code converts the values needs no decoder

The page said Device Detection Engines MUST be able to use the decoder's
Element Data, and that the Element is implemented for .NET. Neither holds
for an on-premise Engine built on the native code in 51degrees-cxx.

- The native code turns 51D_GetHighEntropyValues evidence, from the query
  string or a cookie, into sec-ch-ua headers before it matches
  (setGetHighEntropyValuesHeader, src/dd/hash/hash.c lines 3540 to 3556,
  called from resultsHashFromEvidence_setSpecialHeaders at line 4313), while
  special evidence processing is on, which is the default
  (src/dd/config-dd.h line 90), all at 51degrees-cxx
  e866f6d3cea936587790f87967eff7863778b30e. The C++ Engine accepts both
  keys (src/dd/EngineDeviceDetection.cpp lines 39 and 41).
- 51degrees-dotnet at 4d7152bfcb7c9bcb17d14cb512025b65a3307313, whose cxx
  submodule is e866f6d3, has no decoder element. Its examples say the engine
  consumes the values as query or cookie parameters
  (examples/device-detection/ExampleBase/ExampleUtils.cs lines 620 to 624).
- 51degrees-java at f69da52e52104687206d1ca1c3595d2935790f34, with the same
  cxx submodule, has no decoder element either.

The page now says an on-premise Engine whose native code converts the
values itself need not read the Element's data, and that a Pipeline built
around one does not need the Element. The precedence rule under Process is
unchanged.

Follows 51Degrees/51degrees-rust#350.

* Say which one value the native code converts
* Say how each form of IP address is looked up

An IPv4-mapped IPv6 address is its IPv4 address under RFC 4291 and RFC 4038, an IPv4-compatible address is deprecated, and an IPv4-embedded address under RFC 6052 stays an IPv6 address. Recording this here gives every language one rule and code comments one URL to point at.

* Pad the runtime table and clear the .NET row's wording
…om the reserved names (#138)

* Say that the Cloud Request Engine sends every Evidence value apart from the reserved names

Processing said the Engine filters the Evidence down to the keys the server needs, and then that it adds all the values from the Flow Data Evidence. James Rosewell decided on 10 November that the Engine sends everything but the reserved names, because what the server uses changes and knowing about other Evidence is important to continued optimization. Accepted Evidence now says it does not limit what is sent.

* Leave the web request view out of the cloud request

web-integration.md has every web integration add a read-only view of the request under server.http-request. .NET and Java leave it out of the cloud request, as it is an object and not a value, so Processing now names it with the reserved names.

* Say letter case is ignored when the web request view is left out of the cloud request

* Restore the line endings that the previous commit changed in six files
* Say what happens when a data file refresh fails

A refresh whose new data cannot be used fails and leaves the Engine on the data it had, the failure is logged and reported as a failure, and polling and the file watcher carry on, so a later good file is still applied.

* Report every data update check, a skipped or not-needed one as not needed

James Rosewell decided on 10 November that the specification follows .NET's DataUpdateService.cs, which reports each polled check, each programmatic check of a configured data file and each file watcher event to its CheckForUpdateComplete event with a success, not needed or failure status. A new section, Reporting each check, says so, and the polling, newFileAvailable and programmatic update paths point to it.

* Follow .NET exactly for data update reporting and errors, and exempt one-request packages

James Rosewell decided on 10 November that data-updates.md follows the .NET data update service exactly. A programmatic check for a data file with no configuration and an update from memory report nothing, a watcher event that waited for another event for the same file is reported as in progress, and a programmatic failure is passed to the caller without being logged. The polling branch of Programmatic update reports not needed. The Errors table takes .NET's messages, levels and statuses. A package whose processes last for one request, such as PHP's, has no data update service.

* Add the update callback error, end the memory refresh message with a full stop, and widen In progress

* Give the callback and integrity check rows exactly as .NET writes them

* Say how a callback's data update error is logged and that later callbacks are skipped
…ry Property by position (#137)

* Scope the read-by-name rule for an Engine created with no names to the C++ Results classes

In C, each property's index is its own place when no names are given (src/common/properties.c lines 479 to 491 in 51degrees-cxx at cfad8978), so an implementation that calls C may read such an Engine by position. The C++ ResultsHash and ResultsIpi classes refuse every position without names (ResultsBase.cpp lines 481 to 484), so the MUST read by name stays for them. The native code still gives each position. Also point the Cloud Request Engine's JSON Builder link at #process, the heading json-builder.md has.

* Say which data set a read by name asks for the position

C resolves a position against the data set the results were populated from (51degrees-cxx cfad8978 src/dd/hash/hash.c lines 5896 to 5901, src/ipi/ipi.c line 3342), so a position taken from the current data set after a refresh can name a different Property.

* Write the property index rule into Finding Properties

James Rosewell decided on 10 November that a property's index is its place in the list of names the engine was built with, fixed at build. An engine configured with no names takes every property name in the data set at build as that list, every read uses the index, and a read by name uses a table built once from the build-time names, never from the data set. This replaces the earlier text that let C read an engine with no names by its data set position. The C quote, the C++ scoping and the json-builder anchor fix stay.

* Say the native code keeps the first data file's names for an Engine created with no names

* Say an Engine with no names has every Property of its first data file, and that getProperties can list one with no position

* Say the no-names Engine's names are sorted by name, and that its metadata reports a Property with no position as not available

* Say a Property only a later data file has is excluded from a no-names Engine's configuration

* Link the C code at the commit that merged it into rc

* Link the C code at the commit that merged it into rc
…139)

* Give the exact error text for a JSON Builder with no sequence number

Every implementation gives the same message when no Sequence Element has produced a sequence number, with JSON Bundler in place of JSON Builder in PHP. Where the error is wrapped, the error the caller reads carries the message.

* Say that the JSON Builder error message is the whole text

Nothing is added to the message, such as a documentation link. A prefix that a language's error type writes in front of every error of that type is not part of the message.
Merge main into rc after the 11 November release
…'s tests prove (#141)

* Read by name where an implementation measures it as faster than by position

Finding Properties in both on-premise Engine specifications said every
read MUST use the position. Node.js, .NET Device Detection and PHP read
by name, because each measured a read by name as faster, and the
fastest approach is the one used. Each specification now says every
read uses the position except where the implementation's own
measurement shows a read by name is faster, that the implementation
records the measurement, and lists the implementations that read by
name with a link to each measurement. The rest of the section is
unchanged.

* Say which reads go by name, and link the issues that follow them up

The exception now covers a read that the implementation's own
measurement shows is faster by name, rather than a whole implementation.
.NET Device Detection lists only its TryGet reads as measured faster by
name, and says its Value<T> and list reads also go by name though a
Value<T> read measured slower by name, with the issue that moves them to
the position. The Node.js and PHP items say their measurement was taken
with the Device Detection Engine, and link the issues that time the IP
Intelligence Engine on its own.

* Link the Node.js job rather than the run, and say why it is red

Both on-premise pages linked the Node.js measurement as run 42995016804,
whose conclusion is failure, so a reader who opened it saw a red run and no
figure. Each page now links job 130437032538 in that run, which holds the
figure, in the same form the .NET item already uses.

That job is itself red, so each page now says why in one sentence. The only
two tests that failed in it are timing assertions, and a read by position
being slower is what made them fail, which is the result the item records.

* Make the read by position a SHOULD, with the exception tests prove

James decided that the rule is a SHOULD and that an exception stands
where tests prove it. So both on-premise Engine specifications now say
every read SHOULD use the position, and an implementation MAY read by
name where its own tests prove a read by name is faster. The proof has
to be a measurement a test or a continuous integration run takes and
records, and the implementation records that run in the change that
makes the read go by name.

The rest of the first item is unchanged, so a typed accessor still
resolves its position once, and the table of names to positions is
still built once and MUST NOT be built again from a later data set.

The three implementations that read by name are listed as before, each
with its measurement and the issues that follow it up. The .NET entry
now says plainly that its Value<T> reads, which no test proves faster
by name, are outside the exception.

RFC 2119 terms are upper case, as stylistic-conventions.md asks.

* Promise a run only where there is one

The list says each read by name is given with the run that proves it,
but the .NET Value<T> reads are listed, go by name and have no such
run, which the entry itself says four lines later. The sentence now
reads "and, where there is one, the run that proves it", which holds
for every entry. The same sentence is in both pages and moves together.
autoUpdate is the master switch for polling alone. The file system
watcher runs on its own setting, whether autoUpdate is on or off,
because watching a file downloads nothing. A License Key is needed only
for the two mechanisms that download, polling and the update on
start-up, so an application with no License Key still has its watcher.

The master switch section, Automatic update from File, General config,
both start-up processes and the Defaults section now say so.
* Say why the On-premise Engines do not support a results cache

Footnote 3 of both On-premise Engine specifications said only that the
reference implementations do not allow a cache because of the complexity
it introduces. It now names the complexity, which is coordinating the
lifetime and thread safety of a result with the underlying C code that
owns the memory the result reads from, says a future version will
address it, and tells callers who need a cache to maintain their own
with the cache implementations the Pipeline provides, storing the values
they read rather than the result.

* Say what a results cache measured as worth on the On-premise Engines

Adds the measured benefit to the footnote that explains why a results
cache is not supported, so readers can judge whether their traffic would
gain from maintaining their own. The numbers come from the IP Intelligence
On-Premise Engine in MaxPerformance with an Enterprise data file.

* Leave the results cache measurements out of footnote 3

The figures were measured on 26 September, before the C library changed
when graphs are walked on 2 November, and nobody has measured since, so
whether they still hold is not known. The footnote keeps why a results
cache is not supported, that a future version will address it, and how
callers can keep their own cache of the values they read.

* Run the pull request checks against rc, now the base

* Leave out the promise of a future version, which nothing tracks
…xample naming the script's inputs (#147)

Two pages of the specification say less than the languages do for the releases on Monday 16 November, or say it wrongly.
…sage (#156)

Each 51Degrees package is now released at its own version, but the usage sharing message carries one version, the `Version` element ([line 162](https://github.com/51Degrees/specifications/blob/706de1f0cb3edd560eb7fd7ef3bb19b93a4883dc/pipeline-specification/pipeline-elements/usage-sharing-element.md#L162), described at [lines 189 and 190](https://github.com/51Degrees/specifications/blob/706de1f0cb3edd560eb7fd7ef3bb19b93a4883dc/pipeline-specification/pipeline-elements/usage-sharing-element.md#L189-L190) as "The version of the Pipeline that is being used to generate the xml"). A message therefore cannot say which release of each package is running, and how far a release has spread cannot be measured.
…w to get one (#157)

[Cloud Device Detection](https://github.com/51Degrees/specifications/blob/706de1f0cb3edd560eb7fd7ef3bb19b93a4883dc/device-detection-specification/required-examples.md#L127-L138) says the cloud examples use a Resource Key, and [Execution](https://github.com/51Degrees/specifications/blob/706de1f0cb3edd560eb7fd7ef3bb19b93a4883dc/device-detection-specification/required-examples.md#L20-L23) says the tests run each example with no arguments or settings. Nothing says what a cloud example does when it runs with no Resource Key, which is how it runs from a fresh clone and how its test runs it.
[sequence-element.md lines 39 to 44](https://github.com/51Degrees/specifications/blob/706de1f0cb3edd560eb7fd7ef3bb19b93a4883dc/pipeline-specification/pipeline-elements/sequence-element.md#L39-L44) says "A `query.sequence` value that is not a whole number, or a string made only of the digits 0 to 9 with no sign or spaces, from 0 up to 2,147,483,646 is not present". It does not say what a whole number is when Evidence can hold several kinds of number, so the languages read a floating-point 2.0 differently. Some count it as 2 and others treat it as absent.
)

[Getting Started Console](https://github.com/51Degrees/specifications/blob/706de1f0cb3edd560eb7fd7ef3bb19b93a4883dc/device-detection-specification/required-examples.md#L225-L227) says "Examples MUST be provided that illustrate basic usage of the API for both Cloud and On-Prem, using fluent builder and options file configuration, when executed as a console (command line) application". That can be read as asking for four examples, one for each of Cloud and On-Prem with each way of configuring, or as met when the console examples show both ways between them.
…#146)

[Session tracking](https://github.com/51Degrees/specifications/blob/c8128ca4fa68c6348d76ddb2889bb5087de1aa5f/pipeline-specification/pipeline-elements/usage-sharing-element.md#session-tracking) in the usage sharing element said that "certain Evidence values MUST NOT be included" in the hash used to find repeat Evidence, and gave the sequence number as an example. It now says five things.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Say which data update error row covers a download cut short

3 participants