Skip to content

Commit 41ab7ab

Browse files
add bandits to precompute payload (#121)
* add bandits to precompute payload * obfuscate bandits
1 parent 9ef2bab commit 41ab7ab

File tree

4 files changed

+142
-1
lines changed

4 files changed

+142
-1
lines changed

configuration-wire/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

configuration-wire/generate.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import {createHash} from "node:crypto";
2+
3+
// npx ts-node generate.ts
4+
5+
function hashAndEncode(jsonData: { precomputed: { response: any; }; }) {
6+
const salt = jsonData.precomputed.response.salt;
7+
const flags = jsonData.precomputed.response.flags;
8+
const bandits = jsonData.precomputed.response.bandits;
9+
// Process each flag
10+
for (const [flagKey, flag] of Object.entries(flags)) {
11+
// Hash flag key with salt
12+
const hashedKey = createHash('md5')
13+
.update(salt + flagKey)
14+
.digest('hex');
15+
16+
// Base64 encode specific fields
17+
// @ts-ignore
18+
flag.allocationKey = Buffer.from(flag.allocationKey).toString('base64');
19+
// @ts-ignore
20+
flag.variationKey = Buffer.from(flag.variationKey).toString('base64');
21+
// @ts-ignore
22+
flag.variationValue = Buffer.from(String(flag.variationValue)).toString('base64');
23+
24+
// Process extraLogging if it has properties
25+
// @ts-ignore
26+
if (flag.extraLogging && Object.keys(flag.extraLogging).length > 0) {
27+
const newExtraLogging = {};
28+
// @ts-ignore
29+
for (const [key, value] of Object.entries(flag.extraLogging)) {
30+
const encodedKey = Buffer.from(key).toString('base64');
31+
const encodedValue = Buffer.from(String(value)).toString('base64');
32+
// @ts-ignore
33+
newExtraLogging[encodedKey] = encodedValue;
34+
}
35+
// @ts-ignore
36+
flag.extraLogging = newExtraLogging;
37+
}
38+
39+
// Replace the original flag key with hashed key
40+
flags[hashedKey] = flag;
41+
delete flags[flagKey];
42+
}
43+
44+
// Process each bandit
45+
for (const [banditKey, bandit] of Object.entries(jsonData.precomputed.response.bandits)) {
46+
// Hash flag key with salt
47+
const hashedKey = createHash('md5')
48+
.update(salt + banditKey)
49+
.digest('hex');
50+
51+
// Base64 encode specific fields
52+
// @ts-ignore
53+
bandit.banditKey = Buffer.from(bandit.banditKey).toString('base64');
54+
// @ts-ignore
55+
bandit.action = Buffer.from(bandit.action).toString('base64');
56+
// @ts-ignore
57+
bandit.modelVersion = Buffer.from(bandit.modelVersion).toString('base64');
58+
59+
// Process actionNumericAttributes
60+
// @ts-ignore
61+
if (bandit.actionNumericAttributes && Object.keys(bandit.actionNumericAttributes).length > 0) {
62+
const newNumericAttributes = {};
63+
// @ts-ignore
64+
for (const [key, value] of Object.entries(bandit.actionNumericAttributes)) {
65+
const encodedKey = Buffer.from(key).toString('base64');
66+
const encodedValue = Buffer.from(String(value)).toString('base64');
67+
// @ts-ignore
68+
newNumericAttributes[encodedKey] = encodedValue;
69+
}
70+
// @ts-ignore
71+
bandit.actionNumericAttributes = newNumericAttributes;
72+
}
73+
74+
// Process actionCategoricalAttributes
75+
// @ts-ignore
76+
if (bandit.actionCategoricalAttributes && Object.keys(bandit.actionCategoricalAttributes).length > 0) {
77+
const newCategoricalAttributes = {};
78+
// @ts-ignore
79+
for (const [key, value] of Object.entries(bandit.actionCategoricalAttributes)) {
80+
const encodedKey = Buffer.from(key).toString('base64');
81+
const encodedValue = Buffer.from(String(value)).toString('base64');
82+
// @ts-ignore
83+
newCategoricalAttributes[encodedKey] = encodedValue;
84+
}
85+
// @ts-ignore
86+
bandit.actionCategoricalAttributes = newCategoricalAttributes;
87+
}
88+
89+
// Replace the original bandit key with hashed key
90+
bandits[hashedKey] = bandit;
91+
delete bandits[banditKey];
92+
}
93+
94+
// Set obfuscated to true and stringify flags
95+
jsonData.precomputed.response.salt = salt;
96+
jsonData.precomputed.response.obfuscated = true;
97+
jsonData.precomputed.response = JSON.stringify(jsonData.precomputed.response);
98+
99+
return jsonData;
100+
}
101+
102+
const fs = require('fs');
103+
const inputFile = 'precomputed-v1-deobfuscated.json';
104+
const outputFile ='precomputed-v1.json';
105+
106+
const jsonData = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
107+
const encodedData = hashAndEncode(jsonData);
108+
fs.writeFileSync(outputFile, JSON.stringify(encodedData, null, 2));

configuration-wire/precomputed-v1-deobfuscated.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,38 @@
7575
},
7676
"doLog": true
7777
}
78+
},
79+
"bandits": {
80+
"string-flag": {
81+
"banditKey": "recommendation-model-v1",
82+
"action": "show_red_button",
83+
"actionProbability": 0.85,
84+
"optimalityGap": 0.12,
85+
"modelVersion": "v2.3.1",
86+
"actionNumericAttributes": {
87+
"expectedConversion": 0.23,
88+
"expectedRevenue": 15.75
89+
},
90+
"actionCategoricalAttributes": {
91+
"category": "promotion",
92+
"placement": "home_screen"
93+
}
94+
},
95+
"string-flag-with-extra-logging": {
96+
"banditKey": "content-recommendation",
97+
"action": "featured_content",
98+
"actionProbability": 0.72,
99+
"optimalityGap": 0.08,
100+
"modelVersion": "v1.5.0",
101+
"actionNumericAttributes": {
102+
"expectedEngagement": 0.45,
103+
"timeOnPage": 120.5
104+
},
105+
"actionCategoricalAttributes": {
106+
"contentType": "article",
107+
"theme": "dark"
108+
}
109+
}
78110
}
79111
}
80112
}

configuration-wire/precomputed-v1.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
}
1616
},
1717
"fetchedAt": "2024-11-18T14:23:39.456Z",
18-
"response": "{\"createdAt\":\"2024-11-18T14:23:25.123Z\",\"format\":\"PRECOMPUTED\",\"salt\":\"c29kaXVtY2hsb3JpZGU=\",\"obfuscated\":true,\"environment\":{\"name\":\"Test\"},\"flags\":{\"41a27b85ebdd7b1a5ae367a1a240a214\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjM=\",\"variationKey\":\"dmFyaWF0aW9uLTEyMw==\",\"variationType\":\"STRING\",\"variationValue\":\"cmVk\",\"extraLogging\":{},\"doLog\":true},\"2309e3afb59efcf9675c0a8eaa565879\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjQ=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNA==\",\"variationType\":\"BOOLEAN\",\"variationValue\":\"dHJ1ZQ==\",\"extraLogging\":{},\"doLog\":true},\"06307a361b7f244ca792cc0dc5f264f7\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjU=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNQ==\",\"variationType\":\"INTEGER\",\"variationValue\":\"NDI=\",\"extraLogging\":{},\"doLog\":true},\"60d9c95b958bdfe620111a1ab618c1f2\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjY=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNg==\",\"variationType\":\"NUMERIC\",\"variationValue\":\"My4xNA==\",\"extraLogging\":{},\"doLog\":true},\"155bbb597e48b282ceff3a342f28001f\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjc=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNw==\",\"variationType\":\"JSON\",\"variationValue\":\"eyJrZXkiOiJ2YWx1ZSIsIm51bWJlciI6MTIzfQ==\",\"extraLogging\":{},\"doLog\":true},\"35f919d963a541a0bd28f349f84050fb\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjg=\",\"variationKey\":\"dmFyaWF0aW9uLTEyOA==\",\"variationType\":\"STRING\",\"variationValue\":\"cmVk\",\"extraLogging\":{\"aG9sZG91dEtleQ==\":\"YWN0aXZlSG9sZG91dA==\",\"aG9sZG91dFZhcmlhdGlvbg==\":\"YWxsX3NoaXBwZWQ=\"},\"doLog\":true}}}"
18+
"response": "{\"createdAt\":\"2024-11-18T14:23:25.123Z\",\"format\":\"PRECOMPUTED\",\"salt\":\"c29kaXVtY2hsb3JpZGU=\",\"obfuscated\":true,\"environment\":{\"name\":\"Test\"},\"flags\":{\"41a27b85ebdd7b1a5ae367a1a240a214\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjM=\",\"variationKey\":\"dmFyaWF0aW9uLTEyMw==\",\"variationType\":\"STRING\",\"variationValue\":\"cmVk\",\"extraLogging\":{},\"doLog\":true},\"2309e3afb59efcf9675c0a8eaa565879\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjQ=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNA==\",\"variationType\":\"BOOLEAN\",\"variationValue\":\"dHJ1ZQ==\",\"extraLogging\":{},\"doLog\":true},\"06307a361b7f244ca792cc0dc5f264f7\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjU=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNQ==\",\"variationType\":\"INTEGER\",\"variationValue\":\"NDI=\",\"extraLogging\":{},\"doLog\":true},\"60d9c95b958bdfe620111a1ab618c1f2\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjY=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNg==\",\"variationType\":\"NUMERIC\",\"variationValue\":\"My4xNA==\",\"extraLogging\":{},\"doLog\":true},\"155bbb597e48b282ceff3a342f28001f\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjc=\",\"variationKey\":\"dmFyaWF0aW9uLTEyNw==\",\"variationType\":\"JSON\",\"variationValue\":\"eyJrZXkiOiJ2YWx1ZSIsIm51bWJlciI6MTIzfQ==\",\"extraLogging\":{},\"doLog\":true},\"35f919d963a541a0bd28f349f84050fb\":{\"allocationKey\":\"YWxsb2NhdGlvbi0xMjg=\",\"variationKey\":\"dmFyaWF0aW9uLTEyOA==\",\"variationType\":\"STRING\",\"variationValue\":\"cmVk\",\"extraLogging\":{\"aG9sZG91dEtleQ==\":\"YWN0aXZlSG9sZG91dA==\",\"aG9sZG91dFZhcmlhdGlvbg==\":\"YWxsX3NoaXBwZWQ=\"},\"doLog\":true}},\"bandits\":{\"41a27b85ebdd7b1a5ae367a1a240a214\":{\"banditKey\":\"cmVjb21tZW5kYXRpb24tbW9kZWwtdjE=\",\"action\":\"c2hvd19yZWRfYnV0dG9u\",\"actionProbability\":0.85,\"optimalityGap\":0.12,\"modelVersion\":\"djIuMy4x\",\"actionNumericAttributes\":{\"ZXhwZWN0ZWRDb252ZXJzaW9u\":\"MC4yMw==\",\"ZXhwZWN0ZWRSZXZlbnVl\":\"MTUuNzU=\"},\"actionCategoricalAttributes\":{\"Y2F0ZWdvcnk=\":\"cHJvbW90aW9u\",\"cGxhY2VtZW50\":\"aG9tZV9zY3JlZW4=\"}},\"35f919d963a541a0bd28f349f84050fb\":{\"banditKey\":\"Y29udGVudC1yZWNvbW1lbmRhdGlvbg==\",\"action\":\"ZmVhdHVyZWRfY29udGVudA==\",\"actionProbability\":0.72,\"optimalityGap\":0.08,\"modelVersion\":\"djEuNS4w\",\"actionNumericAttributes\":{\"ZXhwZWN0ZWRFbmdhZ2VtZW50\":\"MC40NQ==\",\"dGltZU9uUGFnZQ==\":\"MTIwLjU=\"},\"actionCategoricalAttributes\":{\"Y29udGVudFR5cGU=\":\"YXJ0aWNsZQ==\",\"dGhlbWU=\":\"ZGFyaw==\"}}}}"
1919
}
2020
}

0 commit comments

Comments
 (0)