Repository navigation
Offer the derived properties, as .NET and Rust do - #89
Merged
Merged
Conversation
Adds derived-properties as a submodule at the root, with the relative URL ../derived-properties, and a package whose DerivedPropertyElement runs the scripts of its scripts folder, a copy of the submodule's. Its tests run every case file in the submodule's tests folder, the scripts under tests/invalid included.
This was referenced Nov 14, 2026
jwrosewell
marked this pull request as ready for review
November 14, 2026 04:01
This was referenced Nov 14, 2026
Merged
Merged
jwrosewell
added a commit
to 51Degrees/documentation
that referenced
this pull request
Nov 15, 2026
No page in the documentation covers the derived property element. .NET and Rust have released it, PHP has it on `rc` from [51degrees-php#89](51Degrees/51degrees-php#89), Go has it on `rc` from [51degrees-go#132](51Degrees/51degrees-go#132), and the cloud uses the same element to give `derived.HumanConfidence` and `derived.IsGdpr`. A reader of the [Pipeline API features](https://51degrees.com/documentation/_pipeline_api__features__index.html) has no way to find it.
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.
PHP had no element for the derived properties that derived-properties defines, so a PHP pipeline could not offer HumanConfidence or IsGdpr, whilst .NET and Rust can. This adds a
pipeline/derivedpropertypackage whoseDerivedPropertyElementruns every script in thescriptsfolder of derived-properties, and tests that run every case file in itstestsfolder, the scripts undertests/invalidincluded.Closes #74.
What the specification says
All quotes are from derived-properties at cc306386, the head of its
mainand the commit the new submodule pins.docs/format-1.md, lines 4 to 6:
The one rule, lines 35 to 39:
When a property is absent, lines 451 to 455:
Lines 491 to 497, on an element added after a source it reads:
Writing YAML, lines 562 to 570:
Two places where the languages are allowed to differ, lines 590 to 605, being the line a fault names, and how strictly a JSON script is read:
docs/testing.md, lines 3 to 6:
Line 177 and 196 to 197:
docs/authoring.md, lines 365 to 384 asks for one information line per script at build naming "the script, its version, the format number, where the script came from (a built in name, a file path or code) and the output property", and at debug level "the compiled model as canonical JSON". Lines 441 to 442 add a warning for a deprecated script. Lines 452 to 464 describe the check when the element is added, where "a source property that no earlier element supplies" and "two elements in one pipeline producing the same derived property name" fail the pipeline build. The word "earlier" there is at odds with format-1.md lines 491 to 494 above, and .NET and Rust accept an element that supplies the property wherever it sits, so PHP follows the format reference, which calls itself the contract.
README.md, lines 80 to 109 names the build parameters
ScriptsandScriptFiles, each "one string with commas between the names", where "a name that holds a comma is wrapped in double quotes".What changed
The reference implementations read were .NET,
pipeline/DerivedPropertyof 51degrees-dotnet, and Rust, thederived-propertiescrate of 51degrees-rust. The PHP code ports the reference tools the specification follows,tools/parse.mjs,validate.mjs,evaluate.mjsandcanonical.mjs, with the same fault paths and messages..gitmodulesaddsderived-propertiesat the repository root with the relative URL../derived-properties, pinned at cc306386, besidejavascript-templates.pipeline/corecommits a copy of the JavaScript templates.pipeline/derivedproperty/scriptsis the same kind of copy of the submodule'sscriptsfolder.ci/update-packages.ps1of the package moves the submodule to the head of derived-properties and refreshes the copy, aspipeline/core/ci/update-packages.ps1does for the templates, andBuiltInScriptsTests::testTheCopyIsTheScriptsFolderOfTheSubmodulefails when the two differ. The package is not on Packagist, as it has no mirror, likepipeline/translation.^5.4 || ^6.4 || ^7.0 || ^8.0, the Symfony project's YAML reader, which is plain PHP and needs no extension. It readsyesandnoas strings, as the core schema the reference tools use does, and its objects for mappings keep an empty mapping apart from an empty list. Composer picks the newest that the PHP of each job supports. JSON scripts are read withjson_decode, the strict reading the specification allows.DerivedPropertyElementwrites oneAspectPropertyValueper script underderived, throughDerivedPropertyData, where names are matched without regard to case. A script with a source property missing has no value, and readingvalueraises the message naming every missing property, which is the PHP pipeline's no value error. It reads a source as PHP elements hold it, by its lower case name first, carrying the source's own no value message or the message of the exception an engine raises for a property it does not have, and taking the heaviest of a weighted list in thevalueandrawweightingshape IP Intelligence gives. A script reads what an earlier script of the element wrote. Missing element data is logged once by each element. PHP keeps nothing between requests, so where the pipeline is built on every request, as it usually is, each request logs it again.ScriptsandScriptFilessettings, soPipelineBuilder::buildFromConfigbuilds the element, andDerivedPropertyElementBuilderoffersaddBuiltInScript,addScriptFileandaddScriptin code, as .NET's builder does. Every fault in every script is reported in oneScriptValidationException. A setting the element does not have is refused, so a setting written wrongly cannot leave its scripts out without a word.pipeline/core/tests/PropertyDatabaseTests.phpholds that building one asks no element for its properties. So only the elements whose data key a script names are asked, an element withfindProperty, as both on-premise engines have, is asked for that one property, and an element listing no properties may supply any, because a cloud engine learns its properties from its first response (CloudEngine::processInternal).name,type(named as on-premise Device Detection names types),category,description,availableandisList, followed by the rest of theOutputblock.composer.jsoninstalls the package from its directory, andci/packages.ps1lists it as a Root package with no mirror, so the full build runs its Unit suite in every job. On the PHP 7.4 job it is left out, as it needs PHP 8.0. The README and AGENTS.md describe the package and the copy.The tests, in
pipeline/derivedproperty/tests:SharedCasesTestsruns the 69 value cases (57 for HumanConfidence and 12 for IsGdpr), each through a real pipeline with a source element giving the case's properties and the script the package carries, and the 29 rejection cases, each refused with a fault at every expected path and mentioning every expected fragment. A missing value must name exactly the properties the case lists.BuiltInScriptsTestschecks the copy against the submodule and builds one element with every built in script.PipelineCheckTestsfollows the Rust crate'stests/pipeline_check.rs, with one more test that an element withfindPropertyis never asked for every property.ElementTestsandScriptTestscover reading the values PHP elements give, logging, settings, metadata, the JSON bundler, YAML and JSON, type inference and conversion.Runs
Each throwaway run uses a workflow that installs the package with Composer against
pipeline/corebeside it and runs its Unit suite on Ubuntu with PHP 8.0, 8.1, 8.3 and 8.5 and on Windows with PHP 8.3. A sixth job archivespipeline/derivedpropertywithgit archive, as a mirror's tag would be served, installs it from Packagist, and runs the built in scripts.rcat 6d95eee, which holds #87 and #77, is merged in with a merge commit, 787e154, with no conflicts. Its tree is the onegit merge-treegives for 9a22bd6 and 6d95eee, so nothing was edited in the merge.pipeline/derivedproperty/srcremoved. Every test job failed, PHP 8.5 with "Tests: 52, Assertions: 5, Errors: 49, Failures: 3", each error a class of the element not being found and each failure a test that expected an exception from the element, and the package job failed withClass "fiftyone\pipeline\derivedproperty\BuiltInScripts" not found.scripts/HumanConfidence.yamlandscripts/IsGdpr.yamlin the archive, installed51degrees/fiftyone.pipeline.core4.5.207 from Packagist, built HumanConfidence 0.8.0 and IsGdpr 0.1.0 asbuilt in HumanConfidenceandbuilt in IsGdpr, and answered IsGdprtrueforFR.testElementDataNotThereIsLoggedOnceByEachElementnow checks that one element logs the message once over two requests, and that a second element logs it again, as a PHP web server that builds the pipeline per request does. It passes in the green run above.tmp/pr-matrix-89-b, started withtools/pr-matrix.pywithout macOS, success. All 8 build jobs passed, Ubuntu PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5 and Windows PHP 8.3, and the two performance comparison jobs were skipped. Every one of the 9Setting 'PullRequestSha'lines gives 86543e8, and Configure printed "Left out 5 macOS entries". Each job from PHP 8.0 installed the package from its directory and ran its Unit suite with "OK (52 tests, 140 assertions)". The PHP 7.4 job left the package out of the root composer install and skipped its tests, because it requires PHP 8.0.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.
What remains
getValueAsBool, which answersvalue == "True"(ResultsBase.cpp lines 118 to 140), so a Boolean property stored asUnknownreaches PHP asfalse. HumanConfidence comparesdevice.IsVisibleanddevice.HasWebDriverwith"Unknown", and where the data file types those properties as Boolean that comparison is never true through that engine. That needs a data file to check, so it was not checked here. .NET reads the stored string where a script compares a property with text, and #95 does the same for PHP once the data files are checked.Output.Namenaming that element. Format 1 as published at cc306386 asksOutput.Nameto match^[A-Za-z][A-Za-z0-9]*$, so PHP follows the format and has no such scripts.ci/packages.ps1withci/packages.json. Whichever of the two merges second addspipeline/derivedpropertyto the other's form of the table, with no mirror and not published.tmp/php-74-quick,tmp/php-74-red,tmp/pr-matrix-89,tmp/php-74-green-bandtmp/pr-matrix-89-bhave been deleted. The runs above stay readable without them.