@adobe/spacecat-shared-utils 1.114.0 → 1.115.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/helpers.js +45 -3
- package/src/strategy-schema.js +39 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-utils-v1.115.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.115.0...@adobe/spacecat-shared-utils-v1.115.1) (2026-05-06)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **utils:** include subpath in resolveCustomerSecretsName to prevent credential collisions ([#1577](https://github.com/adobe/spacecat-shared/issues/1577)) ([db95707](https://github.com/adobe/spacecat-shared/commit/db95707796c53d0fbe22c4fc40fed9ab271dd59c))
|
|
6
|
+
|
|
7
|
+
## [@adobe/spacecat-shared-utils-v1.115.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.114.0...@adobe/spacecat-shared-utils-v1.115.0) (2026-05-04)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **strategy:** add type discriminator and experimentId for Atomic strategies | LLMO-4643 ([#1571](https://github.com/adobe/spacecat-shared/issues/1571)) ([0151ff5](https://github.com/adobe/spacecat-shared/commit/0151ff510f7643021e6c1ccf21298f6d3c07034b))
|
|
12
|
+
|
|
1
13
|
## [@adobe/spacecat-shared-utils-v1.114.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.113.0...@adobe/spacecat-shared-utils-v1.114.0) (2026-05-04)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/package.json
CHANGED
package/src/helpers.js
CHANGED
|
@@ -39,18 +39,60 @@ export function resolveSecretsName(opts, ctx, defaultPath) {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Resolves the name of the customer secrets based on the baseURL.
|
|
42
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* The hostname (per RFC 1035, case-insensitive) and each URL path segment are
|
|
44
|
+
* percent-decoded and individually sanitized: runs of non-alphanumeric characters
|
|
45
|
+
* are replaced with a single `_`, leading/trailing `_` are trimmed, and the
|
|
46
|
+
* result is lowercased. Segments that reduce to empty after sanitization are
|
|
47
|
+
* dropped. Sanitized parts are joined with `__` as the path-segment delimiter.
|
|
48
|
+
*
|
|
49
|
+
* The `__` delimiter cannot appear inside a sanitized segment (any run of
|
|
50
|
+
* non-alphanumeric characters, including `__`, collapses to a single `_`),
|
|
51
|
+
* so URLs that differ in path structure (different number of segments, or
|
|
52
|
+
* segments with different alphanumeric content) produce distinct keys. Note
|
|
53
|
+
* that path segments differing only in punctuation (e.g. `us-kings` vs
|
|
54
|
+
* `us_kings`) produce the same sanitized segment and therefore the same key;
|
|
55
|
+
* this is an inherent limitation of lossy sanitization.
|
|
56
|
+
*
|
|
57
|
+
* Key format: /helix-deploy/spacecat-services/customer-secrets/<host>[__<seg>...]/<version>
|
|
58
|
+
*
|
|
59
|
+
* Examples:
|
|
60
|
+
* https://nba.com -> .../nba_com/<version>
|
|
61
|
+
* https://nba.com/kings -> .../nba_com__kings/<version>
|
|
62
|
+
* https://nba.com/us/kings -> .../nba_com__us__kings/<version>
|
|
63
|
+
*
|
|
64
|
+
* @param {string} baseURL - The base URL (must be http(s) with a hostname).
|
|
43
65
|
* @param {Object} ctx - The context object containing the function version.
|
|
44
66
|
* @returns {string} - The resolved secret name.
|
|
67
|
+
* @since next - key now includes URL path segments; prior versions used hostname
|
|
68
|
+
* only, causing subpath sites on the same domain to share a secret. LLMO-4186.
|
|
45
69
|
*/
|
|
46
70
|
export function resolveCustomerSecretsName(baseURL, ctx) {
|
|
47
71
|
const basePath = '/helix-deploy/spacecat-services/customer-secrets';
|
|
48
|
-
let
|
|
72
|
+
let url;
|
|
49
73
|
try {
|
|
50
|
-
|
|
74
|
+
url = new URL(baseURL);
|
|
51
75
|
} catch {
|
|
52
76
|
throw new Error('Invalid baseURL: must be a valid URL');
|
|
53
77
|
}
|
|
78
|
+
if (!url.hostname || !['http:', 'https:'].includes(url.protocol)) {
|
|
79
|
+
throw new Error('Invalid baseURL: must be an http(s) URL with a hostname');
|
|
80
|
+
}
|
|
81
|
+
const sanitize = (s) => s.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '').toLowerCase();
|
|
82
|
+
const host = sanitize(url.hostname);
|
|
83
|
+
if (!host) {
|
|
84
|
+
throw new Error('Invalid baseURL: hostname reduces to empty after sanitization');
|
|
85
|
+
}
|
|
86
|
+
const segments = url.pathname.split('/').filter(Boolean)
|
|
87
|
+
.map((seg) => {
|
|
88
|
+
let decoded = seg;
|
|
89
|
+
try {
|
|
90
|
+
decoded = decodeURIComponent(seg);
|
|
91
|
+
} catch { /* keep raw on percent-encoded sequences that are not valid UTF-8 */ }
|
|
92
|
+
return sanitize(decoded);
|
|
93
|
+
})
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
const customer = segments.length > 0 ? `${host}__${segments.join('__')}` : host;
|
|
54
96
|
return resolveSecretsName({}, ctx, `${basePath}/${customer}`);
|
|
55
97
|
}
|
|
56
98
|
|
package/src/strategy-schema.js
CHANGED
|
@@ -38,6 +38,10 @@ const strategyGoalType = z.union([
|
|
|
38
38
|
z.string(), // Catchall for future goal types
|
|
39
39
|
]);
|
|
40
40
|
|
|
41
|
+
// Discriminator between Atomic (experiment-driven) and Evolving (iterative) strategies.
|
|
42
|
+
// Existing pre-GA strategies have no `type` field and default to 'evolving' for backward compat.
|
|
43
|
+
const strategyType = z.enum(['atomic', 'evolving']);
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
46
|
* Library opportunity - user-created reusable opportunity template
|
|
43
47
|
*/
|
|
@@ -76,6 +80,7 @@ const strategyPromptSelection = z.object({
|
|
|
76
80
|
*/
|
|
77
81
|
const strategy = z.object({
|
|
78
82
|
id: nonEmptyString,
|
|
83
|
+
type: strategyType.default('evolving'),
|
|
79
84
|
name: nonEmptyString,
|
|
80
85
|
status: workflowStatus,
|
|
81
86
|
url: z.union([z.string(), z.array(z.string())]),
|
|
@@ -90,6 +95,7 @@ const strategy = z.object({
|
|
|
90
95
|
createdBy: z.string().optional(), // Email of strategy creator/owner
|
|
91
96
|
completedAt: z.string().optional(), // ISO 8601 date string
|
|
92
97
|
goalType: strategyGoalType.optional(),
|
|
98
|
+
experimentId: z.uuid().nullable().optional(),
|
|
93
99
|
});
|
|
94
100
|
|
|
95
101
|
/**
|
|
@@ -138,4 +144,37 @@ export const strategyWorkspaceData = z.object({
|
|
|
138
144
|
}
|
|
139
145
|
});
|
|
140
146
|
});
|
|
147
|
+
|
|
148
|
+
// Validate type-based invariants (Atomic vs Evolving)
|
|
149
|
+
strategies.forEach((strat, strategyIndex) => {
|
|
150
|
+
// Atomic must have a non-null experimentId
|
|
151
|
+
if (strat.type === 'atomic' && (strat.experimentId === undefined || strat.experimentId === null)) {
|
|
152
|
+
ctx.addIssue({
|
|
153
|
+
code: 'custom',
|
|
154
|
+
path: ['strategies', strategyIndex, 'experimentId'],
|
|
155
|
+
message: 'Atomic strategies require a non-null experimentId',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Atomic must not carry selectedPrompts (those come from GeoExperiment)
|
|
160
|
+
if (strat.type === 'atomic' && Array.isArray(strat.selectedPrompts) && strat.selectedPrompts.length > 0) {
|
|
161
|
+
ctx.addIssue({
|
|
162
|
+
code: 'custom',
|
|
163
|
+
path: ['strategies', strategyIndex, 'selectedPrompts'],
|
|
164
|
+
message: 'Atomic strategies must not carry selectedPrompts (use GeoExperiment.promptsLocation)',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Evolving: schema is intentionally permissive on `selectedPrompts`. Today's
|
|
169
|
+
// CreateStrategyDialog initializes selectedPrompts as `[]` for newly-created
|
|
170
|
+
// Evolving strategies (no prompt-selection UI yet). Enforcing "must be
|
|
171
|
+
// non-empty" at the schema would reject those in-flight reads + writes.
|
|
172
|
+
//
|
|
173
|
+
// The "newly-created Evolving strategies must include selectedPrompts" rule
|
|
174
|
+
// is deferred to the milestone where Evolving promotes out of co-innovation
|
|
175
|
+
// mode — at that point the schema tightens, the API layer (saveStrategy)
|
|
176
|
+
// adds the creation-time check, and the prompt-selection screen ships.
|
|
177
|
+
// All three land together. Until then, selectedPrompts on Evolving is
|
|
178
|
+
// structurally validated only.
|
|
179
|
+
});
|
|
141
180
|
});
|