-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GPP Phase 3 - Activity control infrastructure #9546
Comments
Is a module name enough, or do we need to pair it with a type? Would I have a reason to block storage for rubicon analytics but allow it for the bid adapter for example? |
Will leave that to @patmmccann. I'd like to keep this simple if possible. |
To clarify:
Also, I'm not sure about using plurals for the condition variables. I would keep them singular and move the semantics of "matches if it's one of" to the array. This makes no difference now, e.g.
would mean "module name is either bidderA or bidderB, and mediatype is either video or banner". But it seems more amenable for possible future expansions like maybe
which could mean "all modules except bidderA or bidderB". (granted, we could still use the plural "modulenames", but it would somehow look off to me). |
Another point: I think we typically camelCase for |
|
The Prebid Server team met today and agreed to use the term 'component' rather than 'modulename'. This is because on the server side, "modules" are quite different from bid adapters and analytics adapters. The term "component" covers all three. Also, we discussed the namespace of components. We're proposing a pattern of COMPONENTTYPE.NAME. e.g.
It's not clear that PBJS needs to be fully in sync with this since bidders should be compatible, but it's worth discussing. Someday the same module could exist on both sides, so perhaps syncing on the namespace is needed. |
Open items:
|
Default ( initial condition without any config )All actions are allowed, except for device storage from bid adapters. missing: a way to express the default for a subset of an activity ("default is DENY only for bidders"). Proposal (NOT syntax) setConfig({
allowActivities: {
accessDevice: {
default: false,
rules: [{
condition: {
componentType: {
not: "bidder"
}
},
allow: true
}]
}
}
}) Device storagebidderSettings.*.storageAllowedCurrent control: bidderSettings.default.storageAllowed = true; Equivalent rule: mergeConfig({
allowActivities: {
accessDevice: {
rules: [{
condition: {
componentType: "bidder"
},
allow: true
}]
}
}
}) Current control: bidderSettings.someBidder.storageAllowed = true; Equivalent rule: mergeConfig({
allowActivities: {
accessDevice: {
rules: [{
condition: {
component: "bidder.someBidder"
},
allow: true
}]
}
}
}) Current (proposed) control: bidderSettings.someBidder.storageAllowed = ['html5']; Equivalent rule: mergeConfig({
allowActivities: {
accessDevice: {
rules: [{
condition: {
component: "bidder.someBidder",
storageMethod: ["html5"]
},
allow: true
}]
}
}
}) deviceAccessCurrent control: setConfig({
deviceAccess: false
}) Equivalent rule: mergeConfig({
allowActivities: {
accessDevice: {
overrides: [{
allow: false
}]
}
}
}) |
GDPRconsent string translation examplesAll purposes denied, enforcePurpose = true mergeConfig({
allowActivities: {
accessDevice: {
// purpose 1 - device storage
rules: [{
condition: {
// by default strictStorageEnforcement = false and core / "vendorless" modules are allowed to ignore consent
firstPartyComponent: false,
},
allow: false
}]
},
invokeComponent: { // NOTE: simplifying `invokeBidder`, `invokeUserid`, etc into this single activity
rules: [
{
// purpose 1 - userId
condition: {
componentType: "userId",
firstPartyComponent: false,
},
allow: false
},
{
// purpose 2 - participate in auction
condition: {
condition: {
componentType: "bidder",
},
allow: false
}
},
{
// purpose 7 - analytics
condition: {
componentType: "analytics"
},
allow: false
}
]
},
syncUser: {
// purpose 1 - user sync
rules: [{
allow: false
}]
}
}
}) Purpose 1 consent denied, enforcePurpose = true, strictStorageEnforcement = true mergeConfig({
allowActivities: {
accessDevice: {
rules: [{
allow: false
}]
},
invokeComponent: {
rules: [
{
// purpose 1 - userId
condition: {
componentType: "userId",
},
allow: false
},
]
},
syncUser: {
// purpose 1 - user sync
rules: [{
allow: false
}]
}
}
}) Purpose 1 consent denied, with vendor exceptions Note that this is a proposal for how the combination of a particular GDPR consent object and mergeConfig({
allowActivities: {
accessDevice: {
rules: [{
condition: {
componentName: {
NOT: [
"exceptedVendorA",
"exceptedVendorB"
]
}
},
allow: false
}]
},
invokeComponent: {
rules: [
{
condition: {
componentType: "userId",
componentName: {
NOT: [
"exceptedVendorA",
"exceptedVendorB"
]
}
},
allow: false
},
]
},
syncUser: {
rules: [{
condition: {
componentName: {
NOT: [
"exceptedVendorA",
"exceptedVendorB"
]
}
},
allow: false,
}]
}
}
}) Purpose 1 consent denied for particular vendors Note: mergeConfig({
allowActivities: {
accessDevice: {
rules: [{
componentName: [
"vendorA", // determined from reverse-GVLID lookup
"vendorC"
],
allow: false
}]
},
invokeComponent: {
rules: [{
condition: {
component: [
"userId.vendorA",
"userId.vendorC"
],
},
allow: false
}]
},
syncUser: {
rules: [{
condition: {
componentName: [
"bidder.vendorA",
"bidder.vendorC"
],
},
allow: false
}]
}
}
}) "New-style" vendor exceptions (always allow a particular vendor, no matter the context)mergeConfig({
allowActivities: {
accessDevice: {
overrides: [{
condition: {
componentName: "vendor"
},
allow: true
}]
}
}
}) |
User syncsyncEnabledCurrent control pbjs.setConfig({
userSync: {
syncEnabled: false
}
}); Equivalent rule: mergeConfig({
allowActivities: {
syncUser: {
rules: [{
allow: false
}]
}
}
}); filterSettingsCurrent control: pbjs.setConfig({
userSync: {
filterSettings: {
iframe: {
bidders: ['def'],
filter: 'exclude'
},
image: {
bidders: ['abc', 'def', 'xyz'],
filter: 'include'
}
}
}
}) Equivalent rule: mergeConfig({
allowActivities: {
syncUser: {
rules: [{
condition: {
syncMethod: 'iframe',
component: ['bidder.def']
},
allow: false
}, {
condition: {
syncMethod: 'image',
component: {
NOT: [
'bidder.abc',
'bidder.def',
'bidder.xyz'
]
}
},
allow: false
}]
}
}
}); |
Summary
List of requirements (in addition to OP)
|
We discussed in PBJS committee, and I appear to be on the losing side of history here, but I'm going to document my concerns as clearly as possible. There seem to be two approaches with different pros/cons. Decentralized Activity ControlsIn this model, multiple modules can independently submit activity configuration. e.g.
In this model, we need to define an implicit conflict resolution algorithm for what happens when multiple sources report different results for a given activity. The proposed algorithm is, I believe:
Centralized Activity ControlsIn this approach, the Activity config is a 'script' that clearly defines how processing takes place.
TradeoffsIn both cases, config is either required or available for all of the systems. The main differences are, I believe:
I will now stand down, and will begin to think about how a nightmare world split between legacy TCF and GPP would look from a Prebid Server perspective. |
Because this was discussed mostly offline; the point of contention is an explicit callout to GPP from activity rules. Taking this as the example:
The "centralized" model: pbjs.setConfig({
allowActivities: {
activityX: {
useGpp: true, // "call GPP for their consent resolution,"
overrides: [{
// "but always allow componentA"
condition: {
component: "componentA"
},
allow: true,
}]
},
activityZ: {
useGpp: false, // "don't bother calling GPP"
overrides: [{
allow: true // "it's always allowed"
}]
}
}
}) versus the "decentralized": pbjs.setConfig({
allowActivities: {
activityX: {
overrides: [{
// "always allow componentA" (because I know that GPP may otherwise set its own rule to deny it)
condition: {
component: "componentA"
},
allow: true,
}]
},
activityZ: {
overrides: [{
allow: true // "it's always allowed"
}]
}
}
}) My retort is that
In JS this could be done through configuration for the GDPR or USP (sub)modules; but that has the disadvantage that it won't necessarily be as easy to transfer to Prebid Server. |
Let's see if we can make some progress on the open items:
For modules, the PBS committee discussed 3 levels. e.g.
PBS has a rule where the ORTB can't be camelCased. Some of these proposed words are quite long and are little harder to read without underscores. e.g. invoke_component is easier to read than invokecomponent. But overall, I don't care so much about this detail. If we want to settle on just camelCase in PBJS that gets translated to lowercase with no underscores for ORTB, ok.
Here, again, PBS would prefer lowercase and consistency between the positive and negative scenarios. How about:
and
I suspect that several of these additional requirements (e.g. firstPartyComponent) aren't relevant for PBS, but it will be fine for PBJS to send them -- they get ignored as needed. |
this seems to confirm that the first item is
Can we have consistency? I also don't mind which way this goes, but I see a problem in mixing
My preference is
and its negation then being
because a NOT operator is much more useful than a NOT IN operator. I would not require this level of verbosity though (at least for JS; I won't judge if you prefer it for the wire). |
To formalize it a bit more:
Example: {
"condition": {
"component": {
"not": {
"not": {
"in": [
"bidder.vendorA",
"bidder.vendorB"
]
}
}
}
}
} means Less contrived example of how this could be expanded in the future: {
"condition": {
"component": {
"not": {
"matches": "bidder.*"
}
}
}
} meaning "component does not match pattern I would also add the following shorthands (for JS only?)
Examples: {
"condition": {
"component": ["bidder.vendorA", "bidder.vendorB"],
}
} translates to {
"condition": {
"component": {
"in": ["bidder.vendorA", "bidder.vendorB"],
}
}
} and {
"condition": {
"component": {
"not": "bidder.vendorA"
}
}
} translates to {
"condition": {
"component": {
"not": {
"in": ["bidder.vendorA"]
}
}
}
} |
We reached an agreement on capitalization; JS wil take camelCase for activity and parameter names, but convert them to snake_case if they need to be sent over ORTB. I updated the examples above to use camel case. Still open:
|
I vote for separate activities. Several reasons:
Based on your comment in the doc, I made a couple of updates. Primarily, I agree that
Yeah, it will be awkward if we get this wrong. We already have a "code", which exists pretty much everywhere:
So seems like the least disruptive approach would be to simply concatenate the type (bidder, analytics, rtd, userid, general) with the single existing code. If we wanted to include "vendor", we would have to retrofit 438 files to break that out. So I'm leaning towards a two-level taxonomy
Further, I would suggest that component names are allowed to contain a vendor as desired. e.g. PBS has defined "prebid.ortb-blocking" as a name, which is ok. It's just further namespacing within that type. At least for now, I don't think we need any rule-targeting functionality beyond componentType and componentName. Maybe someday a |
There is no longer a requirement that activity controls should be serializable and understood by Prebid Server. Because of this, the For example, this: setConfig({
allowActivities: {
fetchBid: { // always allow all bidders
rules: [{
condition: {
componentType: "bidder"
},
allow: true
}]
},
transmitGeo: { // only allow named analytics adapters to get precise geo
default: false,
rules: [{
priority: 1,
condition: {
componentType: { not: "analytics" }
},
allow: false
},{
condition: {
componentName: ["analytics.reporter1"]
},
allow: true
}]
},
...
}
}) could become this: setConfig({
allowActivities: {
fetchBid: { // always allow all bidders
rules: [{
condition: function({componentType}) {
return componentType === 'bidder'
},
allow: true,
}]
},
transmitGeo: { // only allow named analytics adapters to get precise geo
default: false,
rules: [{
priority: 1,
condition: function({componentType}) {
return componentType !== 'analytics'
},
allow: false,
},{
condition: function({componentName}) {
return componentName === "analytics.reporter1"
},
allow: true,
}]
},
...
}
}) |
Splitting up the GPP feature tracked originally by #8991.
Phase 3 is the "Activity Infrastructure". From the document, this phase is described as:
Prebid.js and Prebid Server will define a set of special activities, each of which can be controlled either directly by the publisher or, in the future, via consent modules like GPP.
The activities defined for Prebid.js are in the document section 3.3.3.
The work for this phase includes:
This issue details a proposal for the Page and ORTB configs that control the activities.
Page Level Config
The
condition
logic will initially be kept simple, just modulename, which corresponds to biddercode or modulecode. However, the attributes used in conditions could expand in the future to include country, mediatype, regulation scope, or other fields.Proposed page-level activity controls:
Details
Here's the general form:
Definitions:
Rule Priority
allow: false
, the activity is DENIED.allow: true
, the activity is ALLOWED.default: false
, the activity is DENIED.Conditions
In general, a condition is a series of clauses that are processed separately. Only if all clauses present are true is the entire condition true.
The general forms a condition take:
Requirements:
The following attributes may be seen:
Formal rules of the condition syntax:
{[UNARY_OPERATOR]: VALUE}
or{[BINARY_OPERATOR]: LITERAL}
;{in: a}
{in: [v]}
Request Level Activity Config Example
The use case for request-level override is to support publishers that want slightly different behaviors for different sites across their account.
ORTB-level activity controls are in ext.prebid.allowactivities
Account-Level Activity Config Example
The proposed account-level config follows the same pattern as the request config.
ORTB controls
Prebid Server will support a similar config at both the account-level and the the request level.
See prebid/prebid-server#2591 for the ext.prebid extension proposed.
The text was updated successfully, but these errors were encountered: