Skip to content

Rename the usage is indirect accessor, refuse usage bits 000, and read the four browser factors - #140

Merged
Automation51D merged 5 commits into
mainfrom
feat/cloud-438-and-usage-indirect
Sep 18, 2026
Merged

Automation51D merged 5 commits into
mainfrom
feat/cloud-438-and-usage-indirect

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

What this changes

This is a breaking rename with no alias, so the next release of these
packages is a minor version rather than a patch. The latest published
version on Maven Central is 4.5.36, so the next one is 4.6.0. The version
comes from GitVersion, which reads the commit messages since the last tag,
so this branch carries an empty commit whose message is +semver: minor
and nothing else needs changing. The pom.xml version is not the source
and is left alone.

Three changes reach the 51Did module (pipeline.did) and its example
(pipeline.developer-examples.fodid). Two come from cloud release 4.4.38
and one from the flags byte decision in
specifications pull request 30,
whose did-specification/identifier-layout.md and
did-specification/package-surface.md on the branch
doc/usage-indirect-and-no-empty-usage are the authority.

  1. Bit 3 is renamed. FodId.isUsageFromConsent() is now
    FodId.isUsageIndirect(), with no alias and no deprecated forwarder.
    The documentation says direct against indirect, with a consent string
    as the only indirect signal today.
  2. Usage bits 000 are refused. Usage.NONE is removed, so Usage
    has exactly three values. FodId refuses a payload whose bits 0 to 2
    are all clear with the new status FodIdParseStatus.NO_USAGE, added
    after UNSUPPORTED_PAYLOAD_VERSION. The check comes straight after the
    payload version check. The throwing readers (fromBase64,
    fromByteArray, fromOwid) throw IllegalArgumentException with a
    message saying the usage bits are 000. Usage.fromFlags now throws for
    000 rather than answering a value. The patterns 010, 100, 101
    and 110 keep their current reading. The payload version stays 0.
  3. Four browser factors. RedeemResult lists the factors in the order
    transport, device, browserip, connectionip, asn, platformname,
    platformversion, browsername, browserversion. browser is no longer a
    documented name. A response that still carries only browser keeps it
    under that name (as for any name the client does not know) and fills
    none of the four new names.

The PMP script tag does not appear anywhere in this repository, so the
change to how it takes the resource key needed no change here.

Files

File Change
pipeline.did/src/main/java/fiftyone/pipeline/did/FodId.java isUsageIndirect() replaces isUsageFromConsent(). Refuses usage bits 000 with NO_USAGE and names it in the thrown message. Adds the package-private masks. Throws documentation updated. A misplaced doc comment on valueOrThrow is moved back to that method.
pipeline.did/src/main/java/fiftyone/pipeline/did/FodIdParseStatus.java New NO_USAGE after UNSUPPORTED_PAYLOAD_VERSION. Class documentation no longer says there are exactly two payload statuses.
pipeline.did/src/main/java/fiftyone/pipeline/did/Usage.java NONE removed. fromFlags throws for 000.
pipeline.did/src/main/java/fiftyone/pipeline/did/RedeemResult.java Factor order holds the nine names. getFactors() documentation describes the four new factors, misconfigured, and the old browser name.
pipeline.did/README.md Usage example, usage table (no NONE row), refusal paragraph, isUsageIndirect() section, status table (adds UNSUPPORTED_PAYLOAD_VERSION and NO_USAGE), and the redeem section, which now lists the current context values and has a table of the nine factors.
pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdParseTests.java New tests for the three-value Usage, the other usage patterns, bit 3 set and clear, and the refusal of 000 with its message. Fixtures that used usage bits 000 for an unrelated purpose now set the non-marketing bit.
pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTests.java Fixtures that used flags 0 now set the non-marketing bit.
pipeline.did/src/test/java/fiftyone/pipeline/did/DidClientTests.java The redeem fixture carries the four new factors. New tests for factor order and for the old browser key. The Reserved fixture sets a usage bit.
pipeline.did/src/test/java/fiftyone/pipeline/did/DidClientLiveTests.java Uses the renamed accessor.
pipeline.developer-examples/pipeline.developer-examples.fodid/src/main/java/pipeline/developerexamples/fodid/Main.java Prints Indirect from isUsageIndirect().
pipeline.developer-examples/pipeline.developer-examples.fodid/src/main/java/pipeline/developerexamples/fodid/CreatorContextDemoServer.java Passes each factor to the page as its own word, so misconfigured is no longer turned into mismatch.
pipeline.developer-examples/pipeline.developer-examples.fodid/src/test/java/pipeline/developerexamples/fodid/ExampleTests.java The fixture payload states a usage. The mismatch fixture carries the four new factors, and the test checks that misconfigured is passed through unchanged.

How it was checked

51Did module unit tests (mvn -pl pipeline.did test, run without a
resource key, which is why the four live tests were skipped):

  • before, on main: Tests run: 146, Failures: 0, Errors: 0, Skipped: 4
  • after: Tests run: 152, Failures: 0, Errors: 0, Skipped: 4

Example module: Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, both
before and after.

Each new test fails without the source change and passes with it:

  • With all src/main changes stashed, the test sources do not compile
    (cannot find symbol for isUsageIndirect and NO_USAGE).
  • With only the 000 check disabled, both usageBitsAllClear_* tests fail
    (expected java.lang.IllegalArgumentException to be thrown, but nothing was thrown).
  • With only the old factor order restored,
    redeem_FactorsIterateInTheDocumentedOrder fails, because the four new
    names come back in the JSON parser's order
    ([... asn, browsername, platformversion, browserversion, platformname]).
    redeem_OldBrowserFactor_FillsNoneOfTheFour passes either way, because
    it pins behaviour the client already had.
  • With only the old example mapping restored,
    Redeem_Route_Relays_Factors_On_A_Mismatch fails
    (expected: <misconfigured> but was: <mismatch>).

Against https://cloud.51degrees.com, the four live tests in
DidClientLiveTests ran and passed (Tests run: 4, Failures: 0, Errors: 0, Skipped: 0). A separate check created an identifier with
id.usage=standard and read it with this package:

Field Value
getUsage() STANDARD (standard)
isUsageIndirect() false
getTerms() https://m4ow.uk/mtm/2.txt
getType() PROBABILISTIC
getVersion() VERSION3
payload length 66 bytes, being 37, the Terms byte and a 28 byte creator context section
verifySignatureDetailed VERIFIED
verify true
verify-full then redeem verify-full returned a sealed result. redeem without a licence key answered 200 {"context":"unreadable"}, which parsed as UNREADABLE with no factors, so the four factors could not be seen live from this check.

Outstanding

  • The example page lists factors in the order org.json serialises them,
    not the documented order, because CreatorContextDemoServer.toJson
    builds a JSONObject, which keeps no order.
  • RedeemResult.Context.NOT_CHECKABLE is still public and documented as
    no longer sent by the service.
  • A live redeem that returns factors needs a licence key matched to the
    resource key, which this check did not use.

The same change in the other ports

The Node port of this change is
pipeline-node#202,
which makes the same rename, refuses the same usage bits and reads the same
four factors.

Overlap with other work in this repository

#146 changes the same
demo (CreatorContextDemoServer.java and the demo page). The two were merged
locally in both directions and both merges are clean.

CI on this branch, 17 September 2026

main has been merged into this branch and the +semver: minor commit sits on
top, so the head is 66e034f. The "Pull Requests" workflow was dispatched on
that head with dryrun=true, which builds the branch and merges nothing. Run
35267769781
concluded success with all 15 jobs green across the 11 Java and operating
system configurations. In the Ubuntu_Java_21 job the Maven totals are 635 tests
run, 0 failures, 0 errors and 10 skipped, and there are no error lines in the
log.

Six of those ten skips are the whole of DidClientLiveTests, which asks for the
_51DEGREES_RESOURCE_KEY environment variable and skips itself when it is
absent. The pull request workflow passes its key to Maven as
-DTestResourceKey, a different name, so these tests never run in that workflow
and never have. They were run on this machine against
https://cloud.51degrees.com instead, as set out above. The other four skips are
LogbackHelperTest, one case in ShareUsageElementTests and two runs of the
cloud engine example test, all of which skip on main as well.

This branch merges cleanly with each of #143, #144, #145 and #146. The nightly
checks out the pull request, merges main into it and builds that before it
merges anything, so the combination is tested there as well.

…nd read the four browser factors

Following specifications pull request 30, bit 3 of the Flags byte records
whether the usage is indirect, so isUsageFromConsent() becomes
isUsageIndirect() with no alias. Usage loses NONE, and a payload whose
usage bits are all clear is refused with the new status NO_USAGE, whose
throwing message says the bits are 000. The other usage patterns keep
their reading.

Cloud release 4.4.38 reports platformname, platformversion, browsername
and browserversion in place of the single browser factor, so the redeem
result lists them in the documented order. The creator context example
now relays misconfigured as itself rather than as a mismatch.
The live tests created a 51Did, verified its signature and redeemed a
result that was deliberately unreadable, so nothing ever read a genuine
creator context verdict. This adds the whole round trip with no browser
in it: a marketing 51Did is created, the sealed verdict is fetched the
way a page fetches it, and the licence key redeems it, which has to
report the signature as genuine and the context as verified because one
process made both calls.
@jwrosewell

Copy link
Copy Markdown
Contributor Author

Added one live test, verifyFullThenRedeemWithTheLicence_ReadsTheVerdict in
pipeline.did/src/test/java/fiftyone/pipeline/did/DidClientLiveTests.java.

Why. The live tests created a 51Did, verified its signature offline and through
the cloud, and redeemed a result that was deliberately unreadable, so no test
ever read a genuine creator context verdict. The new test creates a marketing
51Did with id.usage=standard, fetches the sealed verdict from id/verify-full
the way a page fetches it, and redeems it through the client with the licence
key. One process makes both calls, so the connection that created the identifier
is the connection presenting it and the answer has to be signature verified
and context verified. The client offers no verify-full method by design, that
call belonging to the browser, so the test makes it as a plain request.

How it was checked, against https://cloud.51degrees.com on 17 September 2026:

mvn -pl pipeline.did test -Dtest=DidClientLiveTests
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

with _51DEGREES_RESOURCE_KEY and _51DEGREES_LICENSE_KEY set to the 51Did test
key and its licence. Nothing skipped, so all five ran. The whole module is
Tests run: 152, Failures: 0, Errors: 0, Skipped: 0 with those variables set.

The same flow by hand, for the record: creating with id.usage=standard returns
fodid.idprobglobal, id/verify/{resource}?51did=... answers {"valid":true},
id/verify-full/{resource}?51did=... answers {"result":"..."}, and posting
that result to id/redeem with the resource key and licence answers
{"signature":"verified","context":"verified","verifiedAt":...}. No factors
block comes back with a verified verdict, which is what the service documents,
since a verified verdict matched everywhere and there is nothing to diagnose.

Two things worth knowing that are not this branch's fault.

  1. The Cloud 51Did Tests workflow is not running. Every run since 14 September
    ends with the conclusion action_required and starts no jobs at all, on this
    branch and on main, whether started by a pull request or by hand. So the
    live tests are not being run in CI at the moment and the evidence above is
    from a local run.
  2. A browser user agent asking for a 51Did now gets a null with the reason "the
    page has not finished running", which is the create last behaviour working,
    and verify-full with a browser user agent answers asking for the values the
    client script collects. A non-browser caller, which is what these tests are,
    gets an identifier immediately.

The factor breakdown comes only with a mismatch, because a verified
verdict matched everywhere and has nothing to diagnose, so nothing read
the factors at all. This creates the identifier for a stated address
rather than for the connection that asked, which makes the address the
identifier carries differ from the one presenting it, and then reads all
nine factors back by name off the redeemed verdict.

Checked against the live cloud, where the answer is a mismatch with
browserip and asn differing and the other seven verified. Creating
without the stated address gives a verified verdict and the test fails,
which is how the arrangement was proved.
@jwrosewell

Copy link
Copy Markdown
Contributor Author

Second live test added, anIdentifierForAnotherAddress_ReportsEveryFactor, in
the same file.

Why. The factor breakdown only comes with a mismatch, since a verified verdict
matched everywhere and there is nothing to diagnose, so nothing read the factors
at all and the nine names this branch adds were never seen coming back from the
service. The test creates the identifier for a stated address, 192.0.2.10,
which is a documentation address and so is never the address the test's own
connection comes from. When the same connection then presents the identifier,
the address it carries is not the one being checked, the verdict is a mismatch,
and the breakdown arrives.

What the live service answers, 17 September 2026:

{"signature":"verified","context":"mismatch","factors":{
 "transport":"verified","device":"verified","browserip":"mismatch",
 "connectionip":"verified","asn":"mismatch","platformname":"verified",
 "platformversion":"verified","browsername":"verified",
 "browserversion":"verified"},
 "verifiedAt":"2026-09-17T00:32:03Z","secondsSinceVerified":0}

So the nine names this branch reads are the nine the service sends, including
the four browser factors in place of the single browser factor the package
knew before. The test asserts all nine are present and read, and that
browserip is the one that says mismatch.

How it was checked:

mvn -pl pipeline.did test
Tests run: 154, Failures: 0, Errors: 0, Skipped: 0

with _51DEGREES_RESOURCE_KEY and _51DEGREES_LICENSE_KEY set, nothing
skipped, six of those being live tests. Creating without the stated address
gives context: verified and the test fails with
expected:<MISMATCH> but was:<VERIFIED>, which is how the arrangement was
proved rather than assumed.

@jwrosewell
jwrosewell marked this pull request as ready for review September 17, 2026 20:15
@jwrosewell
jwrosewell marked this pull request as draft September 17, 2026 20:39
@jwrosewell
jwrosewell marked this pull request as ready for review September 18, 2026 06:44
@Automation51D
Automation51D merged commit b212cbe into main Sep 18, 2026
16 checks passed
@Automation51D
Automation51D deleted the feat/cloud-438-and-usage-indirect branch September 18, 2026 09:56
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.

2 participants