Repository navigation
Read and store evidence keys without regard to letter case - #71
Merged
Merged
Conversation
The Evidence page of the pipeline specification says keys MUST be case-insensitive, so header.User-Agent and header.user-agent are the same key. Node stores and reads a key exactly as given, so the two are two separate entries today. These tests fail without the fix that follows. They cover reading a key in any case, a key added again in other letters replacing the value rather than adding a second entry, the first spelling being kept, and an element reading evidence a caller added in other letters.
The Evidence page of the pipeline specification says keys MUST be case-insensitive, so header.User-Agent and header.user-agent are the same key. Node stored and read a key exactly as given, so the two spellings became two entries and a caller that added one and read the other got undefined. Header names arrive in whatever letters the client or the framework used, whilst our elements read a fixed lower-case key, so evidence that was supplied could go unseen. Evidence now keeps a map from each key in lower case to the key the value is held under. add reuses the spelling a key was first added with, so a later add replaces the value rather than adding a second entry, and get finds the value from any spelling. The map is written only where the value is actually stored, so a key the element filters reject leaves nothing behind. The element filters already compared without regard to case, so they are unchanged. types/evidence.d.ts follows the source. This matches the shape chosen in Python and PHP.
… letters The JavaScript Builder's filter looks for query. and header. in the letters given, so on a pipeline where it is the only element a key such as Query.Client-IP is thrown away before get can be asked for it. This test fails until the filter ignores letter case.
The JavaScript Builder's evidence filter now lower-cases the key before looking for query. and header., as the basic list and usage sharing filters already do, so whether a key is kept no longer depends on its letters. The table from each key in lower case to its stored spelling is made with no prototype, so a key such as __proto__ or constructor is an ordinary entry in it. The evidence store itself keeps its prototype, because getAll() hands it to callers who may call its methods.
jwrosewell
marked this pull request as ready for review
November 11, 2026 13:53
This was referenced Nov 11, 2026
Merged
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Evidence keys ignore letter case
The problem
Evidence keys are meant to ignore letter case, so
header.User-Agentandheader.user-agentare one key. Node does not do that.Evidence.addstoresthe key exactly as it was given and
Evidence.getlooks it up exactly as it wasgiven, in
pipeline/fiftyone.pipeline.core/evidence.js,so the two spellings become two separate entries in
evidenceStoreand acaller that adds one spelling and reads the other gets
undefined.That matters in real use because the letters a key arrives in are not under our
control. HTTP header names are case-insensitive, so a value added from a request
or by a framework can reach the pipeline in any letters, whilst our own elements
read a fixed lower-case key. The element then sees nothing, and the pipeline
behaves as though the evidence was never supplied.
BasicListEvidenceKeyFilter.filterEvidenceKeyalready compared withtoLowerCase()on both sides, atbasicListEvidenceKeyFilter.jsline 51,so a key in any letters is kept or dropped correctly by it. The JavaScript
Builder's filter, at
javascriptbuilder.jsline 180,did not, and is changed here, as Tests below describes. The links are at
c32a2905, thercthis draft started from.What the specification says
From
pipeline-specification/features/evidence.md,at commit
67ead6986f3118c68f0714325800b92d48ddc71f, line 10:What changed and why
The change follows the shape Python and PHP already chose, because this is all
about alignment across the languages. Python's is
51degrees-python 95
and PHP's is
51degrees-php 63, and both
keep a side map from the key in lower case to the key as it was first added.
Node now does the same.
In
pipeline/fiftyone.pipeline.core/evidence.js:this.keys, the map from a key in lower case to the keythe value is held under in
evidenceStore.addfolds the key to lower case and, where that folded key is already known,reuses the spelling the key was first added with before doing anything else.
So a second add under other letters replaces the first value rather than
making a second entry, and
evidenceStorekeeps one entry per key. The map isonly written in the branch that actually stores the value, so a key the
element filters reject leaves no entry behind and a later add of the same key
is still judged on its own.
getfolds the key to lower case, looks the stored spelling up in the map andreturns that value, returning
undefinedwhere the key is not there, which iswhat it returned before for a key that was never added.
types/evidence.d.tsis updated to match the source, with the newkeysfieldand the two reworded doc comments, because that file is generated from the
JSDoc and a stale declaration would describe behaviour the code no longer has.
getAllis deliberately left alone. It still returnsevidenceStore, which nowholds one entry per key under the letters it was first added with, so a caller
that enumerates evidence sees each key once.
Tests
The tests are in their own commit so a red run is possible from it, before the
fix exists. They are in a new file,
pipeline/fiftyone.pipeline.core/tests/evidenceKeyCase.test.js, and they cover:getAllholds one entry and thelater value wins.
pipeline, which includes the Sequence Element, adds
QUERY.SESSION-IDandQuery.Sequence, and checks the Sequence Element read the sequence andadvanced it, which it can only do through a case-insensitive
get.undefined, which guards the exit pathof
getthat the change rewrote.javascriptbuilder.jsline 180 looked forquery.andheader.in theletters given, so on a pipeline where it is the only element
Query.Client-IPandHEADER.User-Agentwere thrown away beforegetcould be asked for them. Whether a key is kept now ignores letter case as
well, as the basic list filter and the usage sharing filter already did.
Runs
on the tests-only commit
f9d74d6. 4 of the 5 tests inevidenceKeyCase.test.jsfail, and the one that passes is theundefinedcase, which passes before and after and is there to prove the change did
not break the absent-key path.
on branch
tmp/node-evcase-red2, the test commit30b4af9over the storefix. The new test fails and the other 5 pass.
on branch
tmp/node-evcase-green2, the head4f31fff, withevidenceKeyCase.test.jsandjavascriptbuilder.test.js, 45 of 45 pass.rcis merged in at1e077a7to bring the currentcxxsubmodule.4f31fff, success, run 42996638568. Configure and the Ubuntu, Ubuntu ARM and Windows jobs on Node 22 and 24 pass, 1003 of 1005 tests with 2 skipped in each, Configure printed "Left out 2 macOS entries", and all 7Setting 'PullRequestSha'lines are4f31fff2570d8234c91c5254e92c1d097adc897c.4f31fffalso makes the table from each key in lower case to its storedspelling with no prototype, so
__proto__orconstructoris an ordinaryentry in it. The evidence store itself keeps its prototype, as it did,
because
getAll()hands it to callers who may call its methods, so a valueadded under
__proto__is still not stored. That was so before thischange.
What remains
keeps the evidence no element accepts for the Cloud Request Engine, also
changes
Evidence.add, so whichever of the two merges second takes theother in first, and keys Send the cloud every Evidence value apart from the reserved names #72's set aside store through the same lower-case
table, so that two spellings of one key no element accepts are one entry
and are sent once.
macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main.