Skip to content

Split the preference platform documentation by feature, with the load process diagram - #238

Draft
jwrosewell wants to merge 30 commits into
mainfrom
feature/create-last-g
Draft

jwrosewell wants to merge 30 commits into
mainfrom
feature/create-last-g

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

This rewrites the developer documentation for the Preference Management
Platform as a section of nine pages with the load process diagram, so that
publishers are told how the create last flow now works, being two tags, the
client script gathering the visitor's answer itself and a 51Did created only
after every other value is known. It also carries the cloud and 51Did page
updates from #235.

Documentation work package of the create last programme, which is tracked
internally, and the work package issue
#237.

Draft, and it stays a draft. These pages describe behaviour that the
cloud release carrying the Preference Management Platform change
introduces, so they publish with that release and not before.

Merge order

  • Before this merges. The cloud release carrying the cloud service's own
    take of the new client script, with the Preference Management Platform
    change, including the build constants that fix the three second wait and
    replace data-timeout, live in that release or an earlier one, and the
    change that stops the service reading a Global Privacy Platform string
    merged before it. The privacy wording review of the website, which is
    tracked internally, has to happen before this reaches a published release.
  • What waits on this. Nothing. The worked example on cmp-wiring.md
    follows later, once IsGdpr is live.

What this does

src/identifiers/pmp.md was one 83 line page describing an integration
where the client script is loaded from the action URL on every answer.
That is no longer how it works. The page becomes a section of nine, one
per feature, each short enough to read in one sitting.

  • pmp/index.md keeps the Identifiers_PMP page id, so every existing
    link from main.md, fodid.md, overview.md, the Prebid page and the
    WordPress page still resolves. It carries the load process diagram,
    a mermaid sequence with the browser, the page, the loader tag, the
    bundle, the cloud and the client script, followed by the same sequence
    in ten numbered steps.
  • pmp/integration.md, the two tags, load order, the object name and
    data-object-name, the action URL as an optional hook, and what happens
    when the page carries no client script.
  • pmp/preferences.md, the three answers, where each is kept, the window
    event, the getter and the Transparency and Consent Framework surface.
  • pmp/sharing.md, the network name, the second card and its three second
    wait, the shared store and its endpoint.
  • pmp/isgdpr.md, what IsGdpr is and what the platform does with it.
  • pmp/cmp-wiring.md, the page for a site running a consent management
    platform.
  • pmp/cmp-comparison.md, the platform set against a consent management
    platform row by row, and every framework feature with how the platform
    implements it.
  • pmp/configuration.md, the attribute table and the two fixed waits.
  • pmp/privacy.md, draft privacy notice wording.

src/identifiers/fodid.md gains a section on how the answer reaches the
cloud from a browser page, and states that a stated usage is read first
and a consent string beside it is never examined, that a Global Privacy
Platform string is not read at all, and what bit 3 of the flags byte
records. src/identifiers/overview.md gets the platform's name right.

This pull request also carries all of
#235
, James
Rosewell's "Align the cloud and 51Did pages with what the service now
does", as commit 40539e5e0 authored by him, because both rewrite the
51Did page. That brings the match key, the minute date, the
/owid/api/v3/public-key endpoint, the key schedule fields, the TCF
purpose sets from Appendix 1 of the Model Terms, derived properties and
HumanConfidence, and the licence key used from a web browser, across
src/identifiers/fodid.md, src/services/cloud/errormessages.md,
src/services/cloud/overview.md and src/services/cloud/resourcekeys.md.
The lines applied are exactly the lines of #235. Where #235 and this work
described the same behaviour differently the code decided. #235's purpose
sets match pmp/src/iab-purpose-sets.json and the appendix, so they
replace the stale table. This work's Global Privacy Platform wording
stands over #235's, because the cloud change that stops reading GPP
merges before the release these pages publish with, and #235 was written
before that ruling.

What a reader is told that is new

  • The platform adds the client script only where the page carries no
    client script tag at all, using the cloud that served it and the
    resource key it already holds, and logs that it did. Where a tag is
    there, run or not, it waits for that tag and adds nothing. A publisher
    who writes the tag themselves keeps control of its parameters.
  • data-object-name is optional and fod is used when it is absent.
  • The client script gathers the visitor's answer itself, so the publisher
    writes no code and the action URL is a hook of their own rather than the
    way the script is loaded.
  • The second card is shown only where third party cookies are confirmed to
    work. When the configuration allows third party cookies and the browser
    may support them, the platform waits up to three seconds (3000
    milliseconds)
    after the first card for the client script to confirm
    that they work, and if they are not confirmed in that time there is no
    second card and the answer stays with the site. The platform never waits
    for the 51Did, the refresh or IsGdpr, and where the result is already
    settled when the visitor answers, nothing waits. A result that is not known, a
    key without ThirdPartyCookiesEnabled or
    ThirdPartyCookiesEnabledJavaScript, and a client script that never
    answers all mean no second card. Where sharing is not configured there is
    no second card and no wait. The result comes from the client script's
    device.thirdpartycookiesenabled.
  • data-timeout is gone. The platform waits a fixed 1500 milliseconds
    for the cloud when it reads the shared answer at start up and when it
    writes an answer, and a page that still sets the attribute gets one
    console warning and the attribute is ignored. Both times are set once in
    the platform's build, and the configuration page gives them in a table
    with what happens when each runs out. HTML comments name the two build
    constants for maintainers.
  • That result is a string and not a boolean. It carries 'True' or
    'False', and can carry 'Unknown' or 'NotSupported', so the sharing
    page shows the plain truth test that is wrong and the comparison to make
    instead. IsGdpr is a real boolean, and the two pages say so to each
    other, because a reader who has just read both should not have to guess
    which is which.
  • IsGdpr sets gdprApplies, and its absence is a console warning rather
    than a reason to hide the dialog or withhold an identifier.
  • The identifier is created on the request that carries every snippet
    result, refresh() exists, a change of answer makes a fresh identifier,
    and the iterations of a page view stop at ten.

Where the behaviour came from

Every statement about the client script was read from the template on the
javascript-templates branch of this programme, at commit
eb02894993af45f0bb869f9fcd27426277d30a2e, "fix: send an answer known at
construction when no request would otherwise go". The three console
messages quoted in these pages are verbatim from lines 645, 1197 and 1429
of that file, and var maxIterations = 10 at line 75 is why the page says
ten. The platform pages were read from the pmp/src work in the cloud work
package, including client-script.ts and config.ts. The wait between the
cards and the removal of data-timeout follow James Rosewell's two
decisions of 15 September 2026, and were checked against the working tree
of feature/create-last-c, the platform branch, and then against its commit
a0dbcd56. The two values are thirdPartyCookieWaitMs (3000) and
sharedStoreTimeoutMs (1500) in pmp/build-constants.yaml. The rule for
when the second card is shown comes from offerSharing and decideSharing
in controller.ts and thirdPartyCookiesFrom in client-script.ts, and
the warning comes from warnIfTimeoutSet in config.ts. The pages match that work
from 4b3b06439, and were checked again against a0dbcd56.

Two things a reviewer should look at

  1. Links to 51degrees.com carry no UTM parameters, which is the
    opposite of the convention used elsewhere. This repository's
    .utm-lint.json is { "mode": "forbid" }, because its content is
    published on 51degrees.com, so a campaign tag would overwrite
    attribution. Proved rather than assumed, by adding one correctly tagged
    link and watching utm-lint.ps1 fail on it. The lint is clean on this
    branch.
  2. DoxygenLayout.xml is unchanged, because it enumerates no pages.
    It holds the navigation tabs and the class, file, group and directory
    layouts, and the page tree is built by Doxygen from @page and
    @subpage ordered by src/pageorder.md. The new pages are wired as
    @subpage entries under Identifiers_PMP. Every @ref and @subpage
    target on the branch resolves to a @page id, a heading anchor or an
    @anchor, and nothing is unresolved.

Not finished, on purpose

  • The worked example on cmp-wiring.md waits on IsGdpr reaching the
    cloud. pipeline-dotnet#413
    merged on 15 September 2026, so only the cloud service's own change
    remains, and then its release. The placeholder says so on the page and the
    reason is in an
    HTML comment.
    Everything else on that page is written.
  • pmp/privacy.md is a draft and has not been reviewed by anyone who
    handles customer data protection questions. An HTML comment at the top
    of the page says so. The review is tracked internally, for Tracy Barlow,
    and this pull request should not merge into a published release before it
    happens.
  • The diagram renders, as of 48e4b1794. Doxygen 1.17.0 writes the
    fence out as <pre class="mermaid"> inside a mermaidgraph div and
    renders nothing itself, so the block used to show as a code listing, with
    the numbered walkthrough underneath carrying the same content. The
    mermaid bundle is now held in this repository, copied into the output and
    loaded from docs/header.html only where a page carries a diagram, which
    answers #239.
    It is served from the site rather than from a content delivery network
    because of the Content-Security-Policy 51degrees.com sends, and
    docs/HEADER-NOTES.md carries the reasoning. The walkthrough stays,
    because it is readable without any script at all.

How it was checked

The documentation was not built. ci/generate-documentation.ps1 installs
packages with apt-get, downloads a Linux Doxygen, clones every API
repository and checks out gh-pages, so it is a Linux CI script. Instead:

Review follow up, 15 September 2026

Three commits were added by the review pass of this morning, each checked
against pmp/src on the platform branch feature/create-last-c.

  • caf824126, the wording fixes below, but pushed under the message of an
    unrelated Rust workflow change by mistake, because the review's commit
    message file collided with one another review had written under the same
    name. The content is right and the message is wrong.
  • f26bd7edf, a revert of caf824126, because the branch is never force
    pushed. The revert message says why.
  • 623911969, the same wording fixes again under their own message. The net
    diff of the three commits against 4b3b06439 is four files, 17 lines
    added and 14 removed, and nothing else on the branch changed.

What 623911969 changes, and why.

  • pmp/configuration.md and pmp/integration.md said that leaving
    data-action-url out writes a warning to the console. Config.getValue
    warns only for a mandatory attribute and dispatchAction in
    pmp/src/cloud.ts returns without a message where the template is absent,
    so nothing is written, and both pages now say so.
  • pmp/isgdpr.md gave two reasons for the platform's warning where IsGdpr
    cannot be had. explainMissing in pmp/src/client-script.ts has a third,
    the property present with no value for this visitor, which points at
    derived.isgdprnullreason, so the page lists all three.
  • pmp/preferences.md listed the Framework surface's commands as ping,
    addEventListener and removeEventListener. pmp/src/tcf-api.ts answers
    getTCData as well, and the page already relied on that lower down, so
    the list names it.
  • Four sentences opened with a pronoun whose noun sat a paragraph away, on
    pmp/isgdpr.md, pmp/preferences.md and pmp/configuration.md, and each
    now names the thing.

Verified by a script that asserted exactly one match for each edit before
writing, by reading the diff back, and by utm-lint.ps1 -RepoRoot . -Campaign documentation, which reports clean. The documentation was not
built, for the reason given above.

One claim in the body above was corrected in this edit. The diagram note
under "Not finished, on purpose" said the generator is Doxygen 1.8.15. The
preview at
https://51degrees.github.io/doc-preview/feature-create-last-g/4.5/_identifiers__p_m_p.html
is stamped Doxygen 1.17.0, which writes the fence out as
<pre class="mermaid"> inside a mermaidgraph div, and the page loads no
script that renders that, so the block still shows as a code listing and the
fix is still in docs/header.html rather than in the page, which is
#239. Issue #239
carries the same wrong version and needs the same correction.

Six further commits, pushed on 15 September 2026

The head is now 636ed21cc. Two of the six were written earlier in the
review pass and held back, and four answer James Rosewell's decisions of
this morning.

  • fa7df9f3d, a new page, src/identifiers/pmp/cmp-comparison.md,
    called "Compared With a Consent Management Platform". The page says the
    platform follows the technical schema of the Transparency and Consent
    Framework and presents itself to vendors as a consent management
    platform, that it does not follow the IAB Europe Framework Policies, and
    that what its three answers mean comes from Appendix 1 of the Model Terms
    for Marketing. One table sets the platform against a consent management
    platform row by row, and a second takes each framework feature and says
    how the platform implements it, with "not implemented" stated where that
    is true. Every claim about the platform was read from pmp/src on
    feature/create-last-c. The page joins the section index as a
    @subpage after the wiring page.
  • 9c3100f8b, a link to the new page from pmp/cmp-wiring.md.
  • 0a891f143, the CMP ID, to James Rosewell's decision. The page said
    the platform picked a registered CMP ID at random on each page load from
    a list the build reads. The registration row of the comparison table and
    the CMP ID row of the feature table now say the ID is chosen at random
    each day from IAB Europe's list of registered consent management
    platforms, with no mechanism detail beyond that. A new section, "The CMP
    ID", says why an ID from the list is used at all, being that vendors and
    their tags check the CMP ID in a TC string against that list, so a string
    that follows the technical specification but carries an ID that is not on
    the list is rejected. The same section states that 51Degrees would
    support a change by IAB Europe requiring consent management platforms to
    sign the TC string cryptographically, with the signer identified by a
    decentralised domain name tied to the operator, and that every 51Did is
    already signed that way as an OWID naming its creator's domain. The
    section makes no claim about IAB Europe's plans. The same commit rewords
    four cells of the comparison table that stated more than their sources
    do, being the question asked, refusal, the legal basis and storage.
  • b032413b5, the licence rule, to James Rosewell's decision that the
    documentation must make clear that a licence key without the 51Did
    product cannot create standard or personalized identifiers. The 51Did
    page already said the two marketing usages need the Special license key
    and that the properties come back with a no-value reason without it, and
    the platform page said every answer creates a 51Did. Neither said the
    plain consequence. fodid.md now says, under "Usage policies and
    licensing", that a key without the Special license key creates only
    non-marketing identifiers, that the Special license key is the licence
    key carrying the 51Did product whether it arrives on the Resource Key or
    beside it on the request, and that the platform asks the visitor the
    question whatever the key is entitled to, so only the alternative answer
    creates an identifier on such a key. pmp/index.md qualifies the
    sentence that said all three answers create one.
  • 80c1f0dde, the link to the comparison page from pmp/preferences.md.
    The message of 9c3100f8b says it linked the page from the wiring and
    preferences pages, and it changed only the wiring page, so this commit
    makes that claim true.
  • 636ed21cc, one sentence of "The CMP ID". It said that vendors and the
    tags they put on your page check the CMP ID. The specification says the
    list is used by vendors to decide whether a CMP ID found in a string is
    valid and says nothing about where that check runs, so the page now says
    only what the specification says.

How the five were checked.

  • Every statement added about a consent management platform is taken from a
    source fetched on 15 September 2026, being the
    CMP API specification,
    the
    TC string and vendor list formats specification,
    IAB Europe's page for consent management
    platforms
    , its framework
    page
    and the
    Framework
    Policies
    ,
    version 2026-05-29.5.0.b. The reason for using a registered CMP ID is the
    Global CMP List section of the TC string and vendor list formats
    specification, which says vendors use the list to decide whether the CMP
    ID in a string is valid, and that page section is linked from the new
    text.
  • The licence rule was read from the cloud service's 51Did engine, where
    the usage flags mark non-marketing as needing no licence and the two
    marketing usages as needing one, where the licence lookup accepts only a
    key carrying the 51Did product and otherwise sets the no-value reason,
    and where non-marketing falls back to licence id 0. The platform's own
    source reads no product, entitlement or licence information at all before
    showing the dialog, which is why the question is still asked on a key
    that cannot create a marketing identifier.
  • Every @ref and @subpage target on the branch still resolves to a
    @page id, a {#id} heading anchor or an @anchor, with nothing
    unresolved, over 424 references and 171 pages.
  • The branch carries no UTM parameter, which is what this repository's
    .utm-lint.json demands, and no em dash, no bare "Did" and no
    "privacy-safe" or "anonymous" about the identifier.
  • The documentation was not built locally, for the reason given above. The
    Preview check passed on 636ed21cc in 3 minutes 7 seconds and the UTM
    link lint passed with it. The published preview carries the new page at
    https://51degrees.github.io/doc-preview/feature-create-last-g/4.5/_identifiers__p_m_p__cmp_comparison.html,
    in the navigation under the Preference Management Platform section after
    the wiring page, with "The CMP ID" as one of its sections, and the
    platform index and preferences pages both link to it. The 51Did page
    carries the licence passage.

Two claims in the body above were corrected in this edit. The section is
nine pages rather than eight, in the opening paragraph and under "What this
does", and the new comparison page is listed with the others.

One thing to watch on merge order. The page now says the CMP ID is
chosen once a day. That change to the platform was being made on
feature/create-last-c, the platform branch, while this was written, so
these pages must not publish in a release that does not carry it. The merge
order above already requires that release to carry the platform change.

The check that the platform code makes the choice daily, and that it takes
the list as IAB Europe's registered platforms, belongs to whoever reviews
the platform change.

Five further commits for the consent signals, pushed on 15 September 2026

The head after these five was 004f3ffd3, which merges 636ed21cc into this work. These
answer James Rosewell's ask for a 51Did sub page per consent signal, and
three further items from this morning.

  • b12338f09, four new sub pages of the 51Did page, one per signal, in
    src/identifiers/fodid/, wired as @subpage entries under a new "In
    This Section" heading on fodid.md with a sentence pointing at them from
    the section on consent.
    • fodid/pmp.md, "The Preference Management Platform Answer". The three
      answers and the usage each creates, the answer reaching the cloud as a
      stated id.usage so the signal source bit stays clear, the order the
      client script and the cloud both apply, and what a key without the
      Special license key produces, being non-marketing identifiers only.
      Links the platform section and the comparison page.
    • fodid/tcf.md, "Transparency and Consent Framework Strings". Sending
      the string as tcstring in the query string or a form field and not as
      a header, the two purpose sets of Appendix 1 of the Model Terms for
      Marketing, legitimate interest, Special Purpose 2 going unchecked,
      personalized tried before standard, a stated usage winning with the
      string beside it never examined, and the signal source bit set.
    • fodid/gpp.md, "Global Privacy Platform and US Privacy Strings".
      Neither is read, under gpp or gppstring and for every section
      including the United States national one, a framework string beside one
      still being read as usual, why the terms decide it, what to do instead,
      and the evidencekeys endpoint as the way to tell what a service reads.
    • fodid/gpc.md, "Global Privacy Control". The Sec-GPC header and
      navigator.globalPrivacyControl are ignored, they set no usage and
      change no value inside the identifier, and a publisher who wants to act
      on the signal does so in their own code before asking for an identifier.
  • 42e26908f, onChange rather than complete in every example that
    reads the identifier or a value that arrives with it
    , on
    pmp/integration.md, pmp/isgdpr.md, pmp/cmp-wiring.md,
    pmp/sharing.md twice and devicedetection/features/thirdpartycookies.md,
    each now testing that the section is present before reading it. In the
    client script template at f47917c, complete(fn) calls fn at once
    when the round has already finished and registers nothing, so it never
    fires again, while onChange(fn) catches up and fires on every later
    response. On the common path the visitor answers after the first round,
    so the identifier arrives in a later one. The integration page says that
    in one sentence. The same commit corrects the device detection page,
    which called ThirdPartyCookiesEnabled a boolean and tested it for
    truth, when it is a string property read as True or False.
  • 48e4b1794, the load process diagram now renders, which is
    #239, and six
    unused aliases are dropped. docs/mermaid.min.js is the mermaid 11.17.2
    bundle, docs/Doxyfile copies it into the output with
    HTML_EXTRA_FILES, and docs/header.html fetches it on
    DOMContentLoaded only where the page carries a diagram, then calls
    mermaid.run(). It is served from the site rather than from a content
    delivery network because 51degrees.com sends a Content-Security-Policy
    whose script-src is 'self' and three named hosts, read from the
    response headers of the published documentation, so a jsdelivr or cdnjs
    script would draw the diagram on this preview, which sets no policy, and
    fail silently once published. docs/HEADER-NOTES.md carries that
    reasoning, as that file asks. The aliases dropped are pmp, Pmp,
    pmpsharing, pmpprivacy, cmpwiring and fiftyonedid, none of which
    any page uses.
  • 703079e8b, what a request with no usage actually gets back.
    fodid.md and pmp/cmp-wiring.md said the fodid.* properties come
    back with a no-value reason where no usage could be worked out. The
    service returns no fodid section at all. DidOnPremiseEngine.Process
    runs the engine only where the flow data carries one of the id.usage
    evidence keys, the service ReadMe says the same in its notes on the input
    parameters, and the integration tests Partial_TcString_DoesNotResolve51Did
    and Malformed_TcString_DoesNotResolve51Did assert the section is absent
    while values=FODiD.IdProbLic is on the request. A no-value reason is
    what the two cases where the engine does run and cannot finish produce,
    being a usage the licence does not cover and a value that is not one of
    the three, and the second also carries a warning.
  • 9696dde2d, a cold read of the two new pages that named the thing in
    three places where a pronoun or a gesture stood in for it.

How these were checked.

  • The documentation was built, with Doxygen 1.17.0, the version
    ci/generate-documentation.ps1 downloads, run on this branch with only
    OUTPUT_DIRECTORY overridden. The four pages are generated, they appear
    in the navigation under the 51Did page, and the only unresolved
    references in the whole build are two that predate this work, on
    devicedetection/migrationguides/wurfl.md and
    devicedetection/otherintegrations/uaparser.md.
  • The diagram was rendered and looked at. The generated page was served
    locally and loaded in Chromium, where the mermaid block became one SVG of
    1024 by 1401 pixels. A page with no diagram fetched the bundle zero
    times, and the diagram still drew when the response carried the
    Content-Security-Policy that 51degrees.com sends.
  • Every statement on the four pages was read from source. The purpose sets
    are pmp/src/iab-purpose-sets.json in the cloud repository and Appendix 1
    of https://m4ow.uk/mtm/2.txt, which was fetched and read. The read path,
    the stated usage winning and the signal source bit are IabTcfElement and
    DidOnPremiseEngine on feature/create-last-d1, the branch that stops the
    service reading a Global Privacy Platform string. The licence rule
    is that engine's licence lookup and the CloudV5FODiD product. The three
    answers and how they travel are the platform's controller.ts and the
    client script template at f47917c.
  • Nothing anywhere reads Global Privacy Control or a US Privacy string.
    sec-gpc, globalPrivacyControl, gpc, us_privacy, usprivacy and
    uspstring return no match on the cloud's main, on all six create last
    cloud branches, on the hot fix branch, in the client script template or
    in the platform source.
  • No release is named for the platform string rule, because that change
    names none. Its own commit 9bddab5e took 4.4.37 out of the service
    ReadMe and the 51Did context note, leaving the version only in the test
    gates where a
    comment says to move it if another release is cut first. Production
    answers 4.4.36 today and its evidencekeys list still carries
    query.gpp.
  • Every external link added returns 200, checked with curl -sIL, being
    globalprivacycontrol.org, the W3C draft at w3c.github.io/gpc,
    the IAB Tech Lab Global Privacy Platform and USPrivacy repositories, the
    IAB Europe framework and Framework Policies pages, the IAB Tech Lab
    framework repository and both Model Terms addresses.
  • utm-lint.ps1 -RepoRoot . -Campaign documentation reports clean after
    the merge, and the key scan reports keys found in the commits to push: 0.

One claim in the body above was corrected in this edit. "The diagram does
not render on the published site", under "Not finished, on purpose", is no
longer true and that entry now says what was done and where the reasoning
lives.

The decision that belongs to a person is whether a 3.5 MB third party
library should live in this repository at all, the alternative being a
change to the Content-Security-Policy that 51degrees.com sends, which is not
in this repository and is set by whoever runs the site.

Three further commits on resource keys and sharing, pushed on 15 September 2026

The head is now 4f9d74554. New shared property lists were registered this
morning, and the pages that tell a reader to make a resource key were sending
them to the Configurator's front page, which opens with nothing chosen.

  • 7b258dd29, the Prebid key property list corrected, and a shared list
    explained.
    src/integrations/prebid.md listed the IP intelligence
    properties the Prebid.js real time data module needs without Ip and
    IpV6, although the module writes both to device.ip and device.ipv6.
    Both are added, with a clause saying where each lands. BrowserName,
    BrowserVersion and HumanConfidence are named as three more properties
    worth having on the same key, in their own paragraph rather than in the
    bullets, because the module puts none of the three on the bid request and
    a page reads them from the same cloud answer instead.
    src/services/configurator.md gains one section, "Starting from a shared
    list", with the anchor Configurator_SharedList, saying a shared link
    opens Step 1 with properties already chosen, that the reader still walks
    through Steps 2 and 3 and can tick or clear rows first, and that the
    Configurator adds each chosen property's dependencies as the list opens.
  • 897b4b67d, sharing cannot be undone from the dialog, and how a visitor
    starts again.
    src/identifiers/pmp/sharing.md said how an answer moves
    to the group's store and never said that nothing in the dialog moves it
    back. "Where the Answer Is Held" now says the platform reads this site's
    own storage first and asks the cloud only where this site holds nothing,
    that a successful share removes the copy on this site, and that a later
    change goes to the shared store while sharing is on and is kept here
    instead only where the cloud refuses the write. A short paragraph follows
    saying the second card is offered only to a visitor who has not already
    shared, with the two steps for undoing it, being clearing the group's
    third party cookie and answering Only this site when the question
    comes round again. src/identifiers/pmp/preferences.md gains one line
    where it says where an answer is kept and one after the snippet that
    removes the two local keys, because that snippet asks the visitor again
    only where the answer still lives on this site. The maintainer comment on
    the three second wait no longer names a repository or a path inside one,
    because this repository is public.
  • 4f9d74554, every resource key link points at the ready made list.
    src/identifiers/fodid.md, src/identifiers/fodid/pmp.md, the platform's
    configuration, index, integration and IsGdpr pages, and the Prebid
    integration page now open
    https://configure.51degrees.com/YldpCKbW instead of the Configurator's
    front page, each followed by a pointer to "Starting from a shared list".

How the three were checked.

  • GET https://configure.51degrees.com/props/YldpCKbW returns 64 names,
    read at 14:05 today. Every property the Prebid page and the platform
    pages name was confirmed present in that response, being the six 51Did
    properties, device.PlatformName, device.PlatformVersion,
    device.BrowserName, device.BrowserVersion, derived.HumanConfidence,
    device.ThirdPartyCookiesEnabled,
    device.ThirdPartyCookiesEnabledJavaScript, the ten IP intelligence names
    the module reads and the ten location values.
  • The link answers 200, checked with
    curl -sIL -o /dev/null -w '%{http_code}', as does
    https://configure.51degrees.com, which the Prebid page still names in
    prose.
  • The Prebid property changes were read off the module itself rather than
    off any list. convert51DegreesIpToOrtb2 writes device.ip and
    device.ipv6 from ip.ip and ip.ipv6 with the comment that neither is
    gated on confidence, and it writes the device.geo.* fields only where
    locationconfidence is high or medium. No deepSetValue or
    deepSetNotEmptyValue call in the module reads browsername,
    browserversion or humanconfidence, which is why the paragraph says
    the module does not put them on the bid request.
  • The ip.LocationConfidence dependency example is the deployed catalogue's
    own answer. GET https://configure.51degrees.com/api/data/properties
    gives that property one dependency, location.JavaScript, and 53 other
    properties in the same catalogue depend on the same collector.
  • Every statement added to the sharing and preferences pages was read from
    the platform's own source on the branch that carries this work, being the
    read order, the write path and its fallback, the removal of the local copy
    once a share succeeds, the record of a refusal, and the eligibility test
    on the second card.
  • utm-lint.ps1 -RepoRoot . -Campaign documentation reports clean, and the
    key scan reports keys found in the commits to push: 0.

One claim in the body above was corrected in this edit. The consent signals
section said "The head is now 004f3ffd3", which these three commits make
false, so it now says that was the head after those five.

The Preference Management Platform was one 83 line page that described an
integration where the client script is loaded from the action URL on every
answer. That is no longer how it works, so the page becomes a section with
one page per feature.

src/identifiers/pmp/index.md keeps the Identifiers_PMP page id, so every
existing link still resolves, and carries the load process diagram as a
mermaid sequence with the browser, the page, the loader tag, the bundle,
the cloud and the client script. Beside it are integration, preferences,
sharing, IsGdpr, consent management platform wiring, configuration and
privacy notice wording.

What is new in the behaviour these pages describe:

- The platform adds the client script when the page carries none, using
  the cloud and the resource key it already holds, and logs that it did.
  A publisher who writes the tag themselves keeps control of its
  parameters.
- data-object-name is optional and fod is used when it is absent.
- The client script gathers the answer itself, so the action URL is a
  hook of the publisher's own rather than the way the script is loaded.
- Nothing waits between the two cards, and the third party cookie result
  comes from the client script's device.thirdpartycookiesenabled.
- IsGdpr sets gdprApplies, and its absence is a warning rather than a
  reason to hide the dialog.

src/identifiers/fodid.md gains a section on how the answer reaches the
cloud from a browser page, being the two sources in order, the identifier
being created on the request that carries every snippet result, refresh(),
a change of answer, the iteration maximum, and the record of inputs in
session storage. It also states that a stated usage is read first and a
consent string beside it is never examined, that a Global Privacy Platform
string is not read at all because the Model Terms do not map it, and what
bit 3 of the flags byte records.

The consent management platform page is complete except for the IsGdpr
worked example, which waits on the change that carries the property to the
cloud and is marked as a placeholder with the reason in a comment.

The privacy notice wording is a draft and is marked as awaiting review by
the team who handle customer data protection questions.

Links follow this repository's UTM rule, which is mode forbid in
.utm-lint.json, so 51degrees.com links carry no campaign parameters. The
lint was run against the worktree and is clean.
ThirdPartyCookiesEnabled is declared a string property in the device
detection data, and the cloud's JSON builder lower cases the names of
things and never their values (CloudJsonBuilderElement line 472), so a
page reads device.thirdpartycookiesenabled as 'True' or 'False', and it
can also be 'Unknown' or 'NotSupported' or absent. Every non-empty string
is truthy, so a plain truth test takes the yes branch for a visitor whose
browser blocks third party cookies, which is the reading that matters
most.

The sharing page now says so where the value is read, shows the wrong
test and the comparison to make instead, and treats anything that is
neither word as not knowing rather than as a no. The IsGdpr page says
that its own value is a real boolean and points across, because the two
properties genuinely differ and a reader of both should not have to
guess. The configuration table sends anyone reading their own code to the
sharing page first.
@github-actions

Copy link
Copy Markdown
Contributor

Documentation preview 👀

…second

The integration page said the platform adds the client script when it
finds no client script object on the page. That describes a defect the
browser acceptance tests found rather than the behaviour. Both tags are
asynchronous, so an object that is not there yet says nothing about
whether the page has a client script, and a page carrying both tags was
given a second copy, ran every round twice and created two identifiers.

The platform now asks the page for a client script tag instead, because a
tag is in the page from the moment the browser has read it whether it has
run or not, looks again once the browser has finished reading the page so
a tag written below its own is found, and adds one only where the page
really carries none. Where a tag is there it waits for that tag to run and
says so, and the page quotes that message.

The load process diagram gains the waiting branch, and the index,
sharing and IsGdpr pages say tag where they said client script, so no
page implies that the platform adds one whenever the object is missing.
A tag that runs and leaves no object is now described as warned about
rather than replaced, with the reason.
The section says a script is added only where the page carries no client
script tag, so the heading above it should not say client script.
An audit against a running cloud, then a check of every claim against the
code, found seven things this documentation described the old way, two of
which would have cost a reader working code.

The identifier's date. The 51Did engine now states the minute an
identifier was made (DidOnPremiseEngine.UtcNowToTheMinute, on the cloud
branch fix/51did-creation-minute), which is what the 51Did specification
requires and what the OWID envelope carries. It used to state midnight on
the day of issue, so every identifier issued since midnight looked equally
fresh and a replay within the day left no trace in the date. The 51Did page
now says the date is the minute of issue in UTC, that age is read from it,
that two identifiers issued in the same minute share a date and still
differ in their signatures, and that identifiers from cloud releases up to
4.4.33 carry a date up to a day earlier than the moment they were made. The
comparison example, which said the date changed on every call and showed
two responses "a few seconds apart" with different dates, now shows two
issued in different minutes.

The public key. The 51Did page told readers to fetch the signing key from
GET /owid/api/v3/creator and read publicKeySPKI. That endpoint was dropped
from the OWID specification and answers 404 on the cloud branch
feature/owid-key-period, and the field no longer exists. The section now
describes /owid/api/v3/public-key, which answers JSON carrying the key as
publicKey, the encoding as format, and validFrom and validTo, being the
moments the key came into force and the next key starts. It says to hold
the key against the stated span rather than the date asked for, which is
the point of the change, since one request then serves every identifier in
a period. It also says the only format is spki, that format=pkcs is refused
with 400, that cloud releases up to 4.4.33 answered text/plain with the PEM
alone, and that a client can tell which answer it has from the Content-Type
and 51D-Version headers. The old text dated these changes by the day the
code changed, which a reader of a public page cannot act on, so they are
dated by release instead.

Key selection. The page said the endpoint returns the key with the latest
creation time on or before the date. Selection is by the period in force,
not by creation time. A reader following the old text would have picked a
key by a field that bears no relation to when it was in force.

The key schedule. The /api/v4/id/key example showed two fields where there
are four, and said the datetime filter selects keys created since the
timestamp when it selects on when their period starts. A verifier polling
as documented silently misses keys. The four fields are now listed with
what each is for, weekStart marked as a legacy duplicate not to be read,
and created marked as never being the moment a key came into force. The
schedule is written thirteen weeks ahead by a Monday run
(ci/generate-did-key.py), so created is about three months before a key's
own startsAt, which is what the text now says rather than that the whole
schedule shares one generation moment. The endpoint needs a Resource Key,
where public-key also takes a Licence Key, and the text now says so.

The TCF purposes. The table printed the old sets. Standard asked for 9 and
10, which Appendix 1 of the Model Terms for Marketing does not list, and
did not ask for 2 or 11, which it does, so a consent string that was
exactly the appendix's standard marketing signal produced no identifier at
all. The table now matches the appendix (pmp/src/iab-purpose-sets.json on
the cloud branch feature/mtm-purpose-sets), says the sets come from it
rather than from us, says which releases used the old sets, and says why
Special Purpose 2 is not required.

Licence keys from a browser. The service can refuse a licence key with a
401 when the caller looks like a web browser, on the v4 and OWID routes.
Nothing described it anywhere. There is now an error message entry saying
plainly that the key has not been rejected and there is nothing wrong with
it, why the request is refused, that a resource key is never affected, and
the three ways to resolve it, with the authentication pages pointing at it.

Derived properties and HumanConfidence. Neither was documented. A derived
property is calculated rather than read from a data file, so it arrives in
its own top level derived section and can only be asked for by its
qualified name. The accessible properties engine drops a name it cannot
match, so a request naming only the bare name is answered with the
"properties not available with this subscription" warning, which reads
like an entitlement problem and is not one, and that trap is called out
where a reader meets it. HumanConfidence is described with its three
values and, importantly, that it has no value at all until the 51Degrees
JavaScript has run, because two of the properties it reads, IsVisible and
HasWebDriver, are set on the client. A server only request carries no
value and a null reason naming them. An earlier draft of this change said
such a request tends to answer Low, which the script and the cloud's own
integration test both contradict.

Also corrected: three pages said only the json and js endpoints accept a
licence key alone. The OWID public key endpoint does too, and unlike those
two it needs no values list because its payload is fixed. And the 51Did
page called the stable comparable part FodId.Hash. The published
FiftyOne.Did package calls it FodId.MatchKey, the name the Model Terms use,
carried Hash as a deprecated alias in versions 4.5.85 to 4.5.90, and has
no Hash member at all in 4.5.97, so the page names MatchKey throughout and
says code written against Hash must move.

Carried over unchanged from documentation pull request 235, commit
c8c3f3c, onto the create last branch. Its consent section sits between
the Global Privacy Platform and stated usage wording already here, and the
lines applied are exactly the lines of that commit.
The cloud and 51Did alignment now on this branch documents that a derived
property is dropped when it is asked for by its bare name, and answered
with a warning that reads like an entitlement problem. The IsGdpr page
told a reader to request the property "by name", which leads a reader
listing properties with values straight into that trap, so it now gives
derived.IsGdpr and points at the derived properties section.

It also quotes the property's own published description, from
scripts/IsGdpr.yaml in derived-properties, that the value is a default
for a caller that knows nothing about its own position and not a
determination, because the regulation also reaches an organisation by
where it is established. The page said nothing of that, and a publisher
established in the European Union serving a visitor elsewhere would
otherwise read false as permission.
The pages said nothing waits between the two cards. James Rosewell
narrowed that on 15 September 2026, in his words "The waiting in the PMP
is now only needed whilst it waits for fod.complete. That only happens if
the script is added by the PMP and it has not finished resolving before it
needs the result from 3PC availability."

So the second card follows the first at once, except where the client
script has not called complete when the visitor answers the first card.
Then the platform shows its waiting ring until complete arrives or
data-timeout passes, and decides. The pages describe that as the state
rather than only its usual cause, because the platform's own tests read it
that way (pmp/tests/test-cases/controller.test.ts on the platform branch,
including "a page's own tag still resolving at the click is waited for
too"), and they say plainly that it nearly always means a script the
platform added. Where there is nothing to wait for, being a result already
known, a round that finished without the property, or a client script that
can never answer, the card is decided at once.

- sharing.md gains "When the Second Card Waits", and its third condition
  now says the card is withheld on 'False' and offered on 'True' or an
  unknown, which needs ThirdPartyCookiesEnabled and
  ThirdPartyCookiesEnabledJavaScript on the key. The note that blocking
  browsers never see the card is now conditional on the key carrying the
  property.
- configuration.md says data-timeout bounds the shared read, the shared
  write and that one wait, and gives the cap's reason as the platform
  branch states it, the probe cookie having gone.
- index.md says the same under what the visitor sees, and the load process
  diagram now runs the refresh and the second card side by side, with the
  waiting ring as the one alternative, and step 9 of the walkthrough
  matches it.
The platform branch now gives the 4500 millisecond cap one reason for all
three waits, being that in each of them the visitor can do nothing until
it ends, and says the cards are darkened and cannot be pressed while the
second card waits. The configuration row and the sharing page said the
older, read only reason and did not say the cards cannot be pressed, so
they now say what pmp/src/config.ts and pmp/README.md on that branch say.
… time

James Rosewell corrected the wait between the cards on 15 September 2026:
"The timeout for the transition from card 1 to 2 needs to be set in the
PMP loader and is a constant our side. It's not user configurable. The
2nd card doesn't need to wait for the 51Did, ONLY the decision on the
whether 3PC are available, and only when the configuration allows for the
use of 3PC."

The pages had said data-timeout bounds that wait and that the card waits
for the client script to call complete. Both were wrong, and the platform
branch now does what he said, in commit 88643b8a on feature/create-last-c,
which these pages were checked against.

- data-timeout bounds only the shared read and the shared write, and the
  configuration row says it has nothing to do with the wait between the
  cards. The cap's reason is the shared read again, as config.ts states.
- The wait is short and bounded by a fixed time inside the platform that a
  page cannot change. Its value is not given on any page, and the
  constant, Config.THIRD_PARTY_COOKIE_DECISION_WAIT_MS, is named in an
  HTML comment for maintainers only.
- It waits for the third party cookie decision and nothing else, never for
  the 51Did, the refresh or IsGdpr, and the decision arrives with the first
  response that settles it, even mid round.
- It can only happen where sharing is configured, being
  data-use-third-party-cookies not false and a network name present, and
  otherwise there is no second card, no ring and no wait.
- When the fixed time passes the platform decides from what it holds, the
  likely answer included, so a likely 'False' still withholds the card,
  which decideSharing in controller.ts does.

The load process diagram's second card branch now says "only where sharing
is configured" and the ring waits "for a short fixed time", and the
walkthrough and the visitor section say the same. The diagram was rendered
with mermaid-cli to confirm it still parses.
@jwrosewell jwrosewell added the type: cleanup Tidying that changes no behaviour label Sep 15, 2026
…ata-timeout

James Rosewell made two decisions on 15 September 2026. On the wait
between the cards he said "make sure a single constant in the build process
and that the value is explained in the PMP documentation. i.e. when 3PC is
enabled and available on the browser the PMP waits upto X to validate 3PC
works. If it doesn't then the 2nd card does not display." On the attribute
he said "Can we remove data-timeout from the PMP configuration. If so let's
do it."

The platform branch, feature/create-last-c for cloud pull request 381,
carries both as build constants in pmp/build-constants.yaml, being
thirdPartyCookieWaitMs at 3000 milliseconds and sharedStoreTimeoutMs at
1500 milliseconds. That work was not yet committed when these pages were
checked against its working tree, so the pages depend on it landing.

- The rule is stated with its value on the sharing page, in the visitor
  section and the walkthrough of the index page, and on the configuration
  page. Where the configuration allows third party cookies and the browser
  may support them, the platform waits up to three seconds for the client
  script to confirm that third party cookies work, and if they are not
  confirmed in that time there is no second card and the answer stays
  with the site.
- The second card is now shown only where third party cookies are
  confirmed. The pages had said a result that is not known offers the
  card, including a key without the property and a client script that
  never answers, and all of those now mean no second card, which is what
  decideSharing and thirdPartyCookiesFrom on the platform branch do. The
  third condition, the list of cases where nothing waits, the paragraph on
  blocking browsers, the storage table on the preferences page and the
  integration page's note on a build with no client script now say so.
- The data-timeout row is gone from the attribute table. A new section on
  the configuration page gives both fixed times and what happens when each
  runs out, and says a page still setting data-timeout gets one console
  warning and the attribute is ignored. The sharing page and the
  walkthrough give the 1500 millisecond bound where they describe the
  shared read and the shared write.
- The maintainers' comment named Config.THIRD_PARTY_COOKIE_DECISION_WAIT_MS
  and kept the value off the page. That constant no longer exists and the
  value now belongs on the page, so the comments name the two build
  constants instead.

The load process diagram now shows the three outcomes of the second card
decision, the three second wait with its two endings, and the 1500
millisecond bound on the shared read and the shared write. It was rendered
with mermaid-cli 11.17.0 and checked by eye. The UTM lint is clean and
every @ref and @subpage target resolves.
The drift check compared javascript-builder/assets/JavaScriptResource.mustache
against javascript-templates main on every push and pull request. The
canonical template is being changed by the create last programme,
51Degrees/cloud#373, so the moment that change merges upstream every pull
request in this repository would go red, including ones with nothing to do
with that programme, and the pull request that refreshes the bundled asset
could not be green before the upstream merge.

Push and pull request builds now compare against UPSTREAM_PINNED_REF,
13ba5e7db47f88efc3440377121958b4aee1d3ac, which is the commit the bundled
asset was taken from. The weekly schedule and a manual run still compare
against UPSTREAM_TRACKING_REF, main, so a real upstream change is still
reported once a week, and a failure there is the signal to prepare the
refresh rather than a fault in whatever was pushed. A new step picks one of
the two from the event name and writes both the reference and a plain word
for which kind it is into GITHUB_ENV, so the pass line and the failure
annotation each name the reference compared against and say whether it was
the pinned commit or the tracking branch.

That the bundled asset really matches 13ba5e7 was checked rather than
assumed. The blob git stores for the bundled file and the file served by
raw.githubusercontent.com at that commit are both 34942 bytes with MD5
5bf31dcb4769877af16aeccca1e9c663.

Verified by running the job's own steps locally, read out of this workflow
file, with the bundled copy materialised from the git object because that is
what actions/checkout produces on ubuntu-latest whilst the Windows working
copy carries CRLF under core.autocrlf.

  pull_request       pinned commit     green
  push               pinned commit     green
  schedule           tracking branch   green
  workflow_dispatch  tracking branch   green

cargo build --workspace --all-targets finished in 2m 38s with no warnings,
and cargo test --workspace passed 621 tests with 8 ignored across 69 suites
and no failures.

51Degrees/rust#52 lifts the pin, by moving the bundled asset and
UPSTREAM_PINNED_REF to the same commit in one pull request, which is then
green on its own. The bundled asset is deliberately not touched here.
This reverts commit caf8241. Its content was the documentation wording
fixes of the 15 September 2026 review, but its message belonged to a
Rust workflow change, read from a message file another review had left
under the same name. The same content follows in the next commit under
its own message. Reverted rather than amended, because the commit had
already been pushed and the branch is never force pushed.
…s, and clear four pronouns

Review of 15 September 2026, checked against pmp/src on the platform
branch, feature/create-last-c.

- The configuration and integration pages said that leaving
  data-action-url out writes a warning to the console. Config.getValue
  warns only for a mandatory attribute and dispatchAction in cloud.ts
  returns without a message where the template is absent, so nothing is
  written, and the pages now say so.
- The IsGdpr page gave two reasons for the platform's warning where the
  value cannot be had. explainMissing in client-script.ts has a third,
  the property present with no value for this visitor, which points at
  derived.isgdprnullreason, so the page lists all three.
- The Framework surface answers getTCData as well as ping,
  addEventListener and removeEventListener (tcf-api.ts), and the
  preferences page already relied on that lower down, so the list names
  it.
- Four sentences opened with a pronoun whose noun sat a paragraph away,
  and each now names the thing.

This is the change of caf8241 again. That commit carried another
change's message by mistake and is reverted in the commit before this
one.

UTM lint clean for campaign documentation.
… by feature

One page under the PMP section that says the platform follows the technical schema of the Transparency and Consent Framework and presents itself as a consent management platform, does not follow the IAB Europe Policies, and takes its purpose mapping from Appendix 1 of the Model Terms for Marketing. A table sets the platform against a consent management platform row by row, and a second table takes each framework feature and says how the platform implements it, with not implemented stated where true. Every claim about the platform is read from pmp/src on the create last branch, and every external link answered 200 on 15 September 2026. The page joins the section index after the consent management platform wiring page.
…l, and give 51Degrees' position on signing

James Rosewell's decision of 15 September 2026. The page said the platform picked a registered CMP ID at random on each page load from a list the build reads. The platform is being changed so the choice is made once a day, and the page now says the ID is chosen at random each day from IAB Europe's list of registered consent management platforms, with no mechanism detail beyond that, in the registration row of the comparison table and in the CMP ID row of the feature table.

A new section, The CMP ID, says why an ID from the list is used. Vendors and their tags check the CMP ID in a TC string against that list, which the Global CMP List section of the TC string and vendor list formats specification states, so a string that follows the technical specification but carries an ID that is not on the list is rejected. The same section states that 51Degrees would support a change by IAB Europe requiring consent management platforms to sign the TC string cryptographically, with the signer identified by a decentralised domain name tied to the operator, and that every 51Did is already signed that way as an OWID naming its creator's domain.

Four cells of the comparison table that stated more than the sources do are reworded to what the CMP API specification, the TC string and vendor list formats specification and IAB Europe's own pages say, being the question asked, refusal, the legal basis and storage.
…n-marketing identifiers

James Rosewell's decision of 15 September 2026. The 51Did page said that standard and personalized need the Special license key and that the properties come back with a no-value reason without it, and the platform page said every answer creates a 51Did. Neither said the plain consequence, which is that such a key creates non-marketing identifiers and nothing else.

The 51Did page now says it under Usage policies and licensing, naming the Special license key as the licence key carrying the 51Did product, whether it arrives on the Resource Key or beside it on the request, and adds that the platform asks the visitor the question whatever the key is entitled to, so only the alternative answer creates an identifier on such a key. The platform page qualifies the sentence that said all three answers create one.

Checked in the cloud service's 51Did engine, where the usage flags mark non-marketing as needing no licence and the two marketing usages as needing one, the licence lookup accepts only a key carrying the 51Did product and otherwise sets the no-value reason, and non-marketing falls back to licence id 0. Checked in the platform's own source, which reads no product, entitlement or licence information at all before showing the dialog.
…arry

The commit that added these cross links says it linked the comparison page from the wiring and preferences pages, and it changed only the wiring page. The preferences page ends by saying the platform is not a consent management platform, so a reader there is the one most likely to want the comparison, and its Find Out More list now carries the link.
…heck runs

The CMP ID section said that vendors and the tags they put on your page check the CMP ID in a TC string. The Global CMP List section of the TC string and vendor list formats specification says the list is used by vendors to decide whether a CMP ID found in a string is valid, and says nothing about where that check runs, so the page now says only what the specification says.
…ring, the platform string, Global Privacy Control and the platform answer

The 51Did page said what happens to a Transparency and Consent Framework
string and to a Global Privacy Platform string in two paragraphs, and said
nothing about Global Privacy Control or about a US Privacy string. Four sub
pages now answer one question each, for a developer deciding what a request
will produce.

- fodid/pmp.md, the Preference Management Platform answer reaching the
  cloud as a stated id.usage, the three answers and the usage each creates,
  the signal source bit clear, and what a key without the Special license
  key produces.
- fodid/tcf.md, sending tcstring as a query parameter or a form field and
  not as a header, the purpose sets of Appendix 1 of the Model Terms for
  Marketing version 2, legitimate interest, Special Purpose 2 going
  unchecked, a stated usage winning, and the signal source bit set.
- fodid/gpp.md, a Global Privacy Platform string under either name and a US
  Privacy string deriving no usage, a framework string beside one still
  being read, why the terms decide it, and the evidence keys endpoint as
  the way to tell what a service reads.
- fodid/gpc.md, the Sec-GPC header and navigator.globalPrivacyControl being
  ignored, changing neither the usage nor the value inside the identifier,
  and the two ways a publisher acts on the signal in their own code.

Every statement was read from the source. The purpose sets are
pmp/src/iab-purpose-sets.json and Appendix 1 of https://m4ow.uk/mtm/2.txt.
The read path and the stated usage winning are IabTcfElement. The absent
fodid section is the engine's Process gate and the integration tests that
assert it. The licence gate is TryResolveLicenseId and the CloudV5FODiD
product. Nothing in the service, the client script or the platform mentions
Sec-GPC, globalPrivacyControl, us_privacy, usprivacy or uspstring.

fodid.md gains the four subpage entries and one sentence pointing at them
from the section on consent. No release is named, because the change that
stops a platform string being read names none.
…complete

Every example that read a 51Did, IsGdpr or the third party cookie result
used fod.complete. On the common path the visitor answers after the first
round has ended, so the identifier is created in a later round, and
complete does not deliver it to a handler registered by then.

In the client script template at f47917c, complete(fn) calls fn straight
away when the round has already finished and registers nothing, so it never
fires again. Registered earlier it goes through onChange and does fire
again, which makes the behaviour depend on when the page code ran.
onChange(fn) registers the handler, catches it up when a round has already
finished, and fires on every later response, so it is right either way.

Changed on pmp/integration.md, pmp/isgdpr.md, pmp/cmp-wiring.md,
pmp/sharing.md twice and devicedetection/features/thirdpartycookies.md.
Each example now tests that the section is present before reading it,
because a response carries no fodid section at all until a usage is
available, and no device or derived section unless the resource key asks
for those properties. The integration page says in one sentence why
onChange is the one to use.

The device detection page also called ThirdPartyCookiesEnabled a boolean
and tested it for truth. It is a string property in the device detection
data, read as 'True' or 'False', which pmp/sharing.md already sets out, so
the page now says so and the example compares the text.
Doxygen 1.17.0 writes a fenced mermaid block as <pre class="mermaid"> in a
mermaidgraph div and renders nothing itself, so the load process diagram on
the Preference Management Platform page showed as a code listing. That is
documentation issue 239.

The library is held in this repository rather than fetched from a content
delivery network, because 51degrees.com sends a Content-Security-Policy
whose script-src is 'self' and three named hosts, read from the response
headers of the published documentation on 15 September 2026, so a jsdelivr
or cdnjs script would draw the diagram on the gh-pages preview and fail
silently on the published site.

- docs/mermaid.min.js, the mermaid 11.17.2 bundle from jsdelivr, one self
  contained file with no dynamic imports, MIT licensed with its notices
  inside it. 3.5 MB on disk, about 960 KB compressed on the wire.
- docs/Doxyfile copies it into the output with HTML_EXTRA_FILES. The
  build's minifier skips any file already named *.min.js.
- docs/header.html loads it on DOMContentLoaded, and only where the page
  carries a diagram, then calls mermaid.run(). A page with no diagram
  fetches nothing.
- docs/HEADER-NOTES.md carries the reasoning, as that file asks.

Checked by building the site with Doxygen 1.17.0, the version CI
downloads, and loading the generated page in Chromium. The diagram drew as
one SVG of 1024 by 1401 pixels, a page with no diagram fetched the bundle
zero times, and the diagram still drew with the production
Content-Security-Policy on the response.

Six aliases added with the platform pages are also dropped, being pmp,
Pmp, pmpsharing, pmpprivacy, cmpwiring and fiftyonedid. No page in this
repository uses any of them, every page links with @ref and its own
wording, and an alias carries fixed link text that does not fit the
sentences these pages write.
… fodid section

Two pages said the fodid properties come back with a no-value reason where
no usage could be worked out. The service returns no fodid section at all.

DidOnPremiseEngine.Process runs the engine only where the flow data carries
one of the id.usage evidence keys, being query.id.usage, header.id.usage or
the derived key IabTcfElement writes, so with no usage no element data is
created and the JSON carries no fodid. The service ReadMe says the same in
its notes on the input parameters, and the integration tests assert it,
Partial_TcString_DoesNotResolve51Did and Malformed_TcString_DoesNotResolve51Did
both asserting that the section is absent while values=FODiD.IdProbLic is
on the request.

A no-value reason is what a caller gets for the two cases where the engine
does run and cannot finish, being a usage the licence does not cover and a
usage value that is not one of the three, and the second also carries a
warning. The 51Did page now separates the three outcomes.

The consent management platform page loses the pointer to the section name
on the 51Did page and points at the new framework string page instead,
which is where the purpose sets now live.
…signal

A cold read of the two pages found three places that needed the noun
rather than the gesture.

- The platform answer page said the client script asks a consent
  management platform second, which reads as a page running both. The
  platform serves a framework surface of its own, which is what the
  script's order actually decides between, and that is why an identifier
  made on such a page records a stated usage.
- The same page said 'what changes is the answer' where it meant which
  answer produces an identifier.
- The Global Privacy Control page now says which document decides, being
  Appendix 1 of the Model Terms for Marketing, which maps the framework's
  purposes and no other signal, and its first bullet names the client
  script rather than leaving the actor out.
…rty list opens with

The Prebid integration page listed the IP intelligence properties the RTD
module needs without ip.Ip and ip.IpV6, although the module writes them to
device.ip and device.ipv6 in convert51DegreesIpToOrtb2, which the comment
at modules/51DegreesRtdProvider.js:345 says are not gated on confidence.
Both are added, and the bullet now says where they land.

BrowserName, BrowserVersion and HumanConfidence are named as three more
properties worth having on the same key. The module puts none of them on
the bid request, checked by reading every deepSetValue and
deepSetNotEmptyValue call in the module, so the paragraph says so rather
than implying the module writes them. The HumanConfidence sentence is the
published wording from the Configurator catalogue and the cloud overview
page.

The Configurator page gains one section, Starting from a shared list, with
an anchor the other pages can point at. It says a shared link opens Step 1
with properties already chosen, that the reader still walks through Steps
2 and 3 and can change the selection, and that the Configurator adds each
chosen property's dependencies as the list opens. The dependency example
is ip.LocationConfidence, which the deployed catalogue at
/api/data/properties gives a single dependency, location.JavaScript, and
53 properties in that catalogue depend on the same collector.
…tor starts again

The sharing page told a reader how an answer moves to the group's store and
never said that nothing in the dialog moves it back. The second card is
offered only to a visitor who has not already shared, so it never returns to
ask again, and a reader was left to work that out.

Where the Answer Is Held now carries the whole story. The platform reads
this site's own storage first and asks the cloud only where this site holds
nothing, a successful share removes the copy on this site, and a later
change of answer goes to the shared store while sharing is on and is kept
here instead only where the cloud refuses the write. A short paragraph then
says there is no way back through the dialog, with the two steps for
undoing it, being clearing the group's third party cookie and answering
Only this site when the question comes round again.

The preferences page gains one line where it says where an answer is kept,
and one after the snippet that removes the two local keys, because that
snippet asks the visitor again only where the answer still lives on this
site.

Every statement was read from the platform's own source on the branch that
carries this work, being the read order, the write path and its fallback,
the removal of the local copy once a share succeeds, the record of a
refusal, and the eligibility test on the second card.

The maintainer comment about the three second wait no longer names a private
repository or a path inside it, because this repository is public.
…id and the platform

Six pages and the Prebid integration page sent a reader to the Configurator's
front page, which opens with nothing chosen, so a reader had to know which of
several hundred properties this work needs before they could generate a key
that works.

Every one of them now opens the ready made list at
https://configure.51degrees.com/YldpCKbW, which carries 64 properties,
checked with GET /props/YldpCKbW at 14:05 today. The list holds the six 51Did
properties, the four factor properties device.PlatformName,
device.PlatformVersion, device.BrowserName and device.BrowserVersion,
derived.HumanConfidence, device.ThirdPartyCookiesEnabled and
device.ThirdPartyCookiesEnabledJavaScript, the ten IP intelligence names the
Prebid.js real time data module reads and the ten location values, all
confirmed by name against that response. The link answers 200, checked with
curl -sIL.

Each link is followed by a pointer to Starting from a shared list on the
Configurator page, so a reader knows the Configurator adds dependencies as
the list opens and that they can change the selection before generating.

The pages are the 51Did page, the platform answer page, and the platform's
configuration, index, integration and IsGdpr pages. No link carries a
campaign parameter, because this repository publishes to 51degrees.com and
its .utm-lint.json forbids them, and utm-lint.ps1 reports clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: cleanup Tidying that changes no behaviour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant