harness-mcp-v2 3.2.19 → 3.2.21
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/README.md +146 -75
- package/build/client/harness-client.d.ts.map +1 -1
- package/build/client/harness-client.js +32 -6
- package/build/client/harness-client.js.map +1 -1
- package/build/config.d.ts +10 -0
- package/build/config.d.ts.map +1 -1
- package/build/config.js +15 -0
- package/build/config.js.map +1 -1
- package/build/data/schemas/v0/pipeline.d.ts.map +1 -1
- package/build/data/schemas/v0/pipeline.js +1388 -580
- package/build/data/schemas/v0/pipeline.js.map +1 -1
- package/build/data/schemas/v0/template.d.ts.map +1 -1
- package/build/data/schemas/v0/template.js +1142 -166
- package/build/data/schemas/v0/template.js.map +1 -1
- package/build/data/schemas/v1/pipeline.d.ts.map +1 -1
- package/build/data/schemas/v1/pipeline.js +302 -90
- package/build/data/schemas/v1/pipeline.js.map +1 -1
- package/build/data/schemas/v1/template.d.ts.map +1 -1
- package/build/data/schemas/v1/template.js +708 -488
- package/build/data/schemas/v1/template.js.map +1 -1
- package/build/prompts/deploy-ai-agent.d.ts +3 -0
- package/build/prompts/deploy-ai-agent.d.ts.map +1 -0
- package/build/prompts/deploy-ai-agent.js +300 -0
- package/build/prompts/deploy-ai-agent.js.map +1 -0
- package/build/prompts/feature-flag-rollout.d.ts.map +1 -1
- package/build/prompts/feature-flag-rollout.js +19 -8
- package/build/prompts/feature-flag-rollout.js.map +1 -1
- package/build/prompts/index.d.ts.map +1 -1
- package/build/prompts/index.js +2 -0
- package/build/prompts/index.js.map +1 -1
- package/build/registry/extractors.d.ts +87 -10
- package/build/registry/extractors.d.ts.map +1 -1
- package/build/registry/extractors.js +426 -26
- package/build/registry/extractors.js.map +1 -1
- package/build/registry/index.d.ts.map +1 -1
- package/build/registry/index.js +34 -15
- package/build/registry/index.js.map +1 -1
- package/build/registry/scope-utils.d.ts +45 -0
- package/build/registry/scope-utils.d.ts.map +1 -1
- package/build/registry/scope-utils.js +78 -0
- package/build/registry/scope-utils.js.map +1 -1
- package/build/registry/toolsets/chaos-descriptions.d.ts +72 -20
- package/build/registry/toolsets/chaos-descriptions.d.ts.map +1 -1
- package/build/registry/toolsets/chaos-descriptions.js +435 -140
- package/build/registry/toolsets/chaos-descriptions.js.map +1 -1
- package/build/registry/toolsets/chaos.d.ts.map +1 -1
- package/build/registry/toolsets/chaos.js +967 -193
- package/build/registry/toolsets/chaos.js.map +1 -1
- package/build/registry/toolsets/feature-flags.d.ts.map +1 -1
- package/build/registry/toolsets/feature-flags.js +888 -85
- package/build/registry/toolsets/feature-flags.js.map +1 -1
- package/build/registry/toolsets/governance.d.ts.map +1 -1
- package/build/registry/toolsets/governance.js +4 -0
- package/build/registry/toolsets/governance.js.map +1 -1
- package/build/registry/toolsets/iacm.d.ts.map +1 -1
- package/build/registry/toolsets/iacm.js +787 -11
- package/build/registry/toolsets/iacm.js.map +1 -1
- package/build/registry/toolsets/infrastructure.d.ts.map +1 -1
- package/build/registry/toolsets/infrastructure.js +30 -5
- package/build/registry/toolsets/infrastructure.js.map +1 -1
- package/build/registry/toolsets/pipelines.d.ts.map +1 -1
- package/build/registry/toolsets/pipelines.js +208 -5
- package/build/registry/toolsets/pipelines.js.map +1 -1
- package/build/registry/toolsets/release-management.d.ts +8 -0
- package/build/registry/toolsets/release-management.d.ts.map +1 -0
- package/build/registry/toolsets/release-management.js +783 -0
- package/build/registry/toolsets/release-management.js.map +1 -0
- package/build/registry/toolsets/sto.d.ts.map +1 -1
- package/build/registry/toolsets/sto.js +93 -17
- package/build/registry/toolsets/sto.js.map +1 -1
- package/build/registry/types.d.ts +52 -4
- package/build/registry/types.d.ts.map +1 -1
- package/build/resources/harness-schema.d.ts.map +1 -1
- package/build/resources/harness-schema.js +2 -1
- package/build/resources/harness-schema.js.map +1 -1
- package/build/tools/entity-schema/live.d.ts +6 -5
- package/build/tools/entity-schema/live.d.ts.map +1 -1
- package/build/tools/entity-schema/live.js +114 -10
- package/build/tools/entity-schema/live.js.map +1 -1
- package/build/tools/entity-schema/types.d.ts +7 -1
- package/build/tools/entity-schema/types.d.ts.map +1 -1
- package/build/tools/harness-create.d.ts.map +1 -1
- package/build/tools/harness-create.js +5 -1
- package/build/tools/harness-create.js.map +1 -1
- package/build/tools/harness-delete.d.ts.map +1 -1
- package/build/tools/harness-delete.js +5 -1
- package/build/tools/harness-delete.js.map +1 -1
- package/build/tools/harness-execute.d.ts.map +1 -1
- package/build/tools/harness-execute.js +15 -9
- package/build/tools/harness-execute.js.map +1 -1
- package/build/tools/harness-get.d.ts.map +1 -1
- package/build/tools/harness-get.js +5 -1
- package/build/tools/harness-get.js.map +1 -1
- package/build/tools/harness-list.d.ts.map +1 -1
- package/build/tools/harness-list.js +18 -2
- package/build/tools/harness-list.js.map +1 -1
- package/build/tools/harness-schema.d.ts +2 -1
- package/build/tools/harness-schema.d.ts.map +1 -1
- package/build/tools/harness-schema.js +10 -8
- package/build/tools/harness-schema.js.map +1 -1
- package/build/tools/harness-update.d.ts.map +1 -1
- package/build/tools/harness-update.js +5 -1
- package/build/tools/harness-update.js.map +1 -1
- package/build/tools/index.js +1 -1
- package/build/tools/index.js.map +1 -1
- package/build/utils/url-parser.d.ts +2 -0
- package/build/utils/url-parser.d.ts.map +1 -1
- package/build/utils/url-parser.js +55 -0
- package/build/utils/url-parser.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1,24 +1,105 @@
|
|
|
1
|
-
import { passthrough, fmeListExtract, fmeGetExtract } from "../extractors.js";
|
|
1
|
+
import { passthrough, fmeListExtract, fmeGetExtract, fmeV4PaginatedListExtract } from "../extractors.js";
|
|
2
|
+
import { isFmeHarnessNativeSelected, logFmeDeprecation, requireFmeIdentifier, requireHarnessNativeSegmentScope, resolveFmeDualMode } from "../scope-utils.js";
|
|
2
3
|
const fmeActionExtract = (raw) => {
|
|
3
4
|
if (raw !== null && typeof raw === "object" && !Array.isArray(raw))
|
|
4
5
|
return raw;
|
|
5
6
|
return { success: true, result: raw };
|
|
6
7
|
};
|
|
8
|
+
// The confirmed Harness-native FME v4 API (/fme/api/v4/...) does not use the
|
|
9
|
+
// standard NG orgIdentifier/projectIdentifier query param convention — it expects
|
|
10
|
+
// account_id/organization_identifier/project_identifier instead (confirmed live
|
|
11
|
+
// against qa.harness.io). Applied per-route (never resource-level) on dual-mode
|
|
12
|
+
// resources so the legacy Split.io branch's wire format stays untouched.
|
|
13
|
+
const FME_HARNESS_NATIVE_SCOPE_PARAMS = {
|
|
14
|
+
account: "account_id",
|
|
15
|
+
org: "organization_identifier",
|
|
16
|
+
project: "project_identifier",
|
|
17
|
+
};
|
|
18
|
+
// Harness-native v4 create bodies (CreateFeatureFlagRequest/CreateSegmentRequest,
|
|
19
|
+
// confirmed against Harness_Split/Main) accept tags as [{name: string}]; callers
|
|
20
|
+
// commonly pass bare strings instead, so normalize the same way update already does.
|
|
21
|
+
function normalizeFmeTags(tags) {
|
|
22
|
+
return Array.isArray(tags) ? tags.map((t) => (typeof t === "string" ? { name: t } : t)) : tags;
|
|
23
|
+
}
|
|
24
|
+
const FME_SEGMENT_TYPES = ["standard", "rule_based", "large"];
|
|
7
25
|
const fmeFeatureFlagUpdateSchema = {
|
|
8
|
-
description: "Partial update for an FME feature flag's metadata. Provide the fields you want to change
|
|
26
|
+
description: "Partial update for an FME feature flag's metadata. Provide the fields you want to change. Legacy mode (workspace_id): converted to JSON Patch (RFC 6902) automatically — description/tags/rolloutStatus only. Harness-native mode (org_id+project_id): sent as JSON Merge Patch (RFC 7396) — description/tags/owners/rolloutStatus; set description/tags/owners to null (or [] for tags/owners) to clear them, omit a field to leave it unchanged.",
|
|
9
27
|
fields: [
|
|
10
28
|
{ name: "description", type: "string", required: false, description: "Updated description" },
|
|
11
29
|
{ name: "tags", type: "array", required: false, description: "Updated tags — provide as [{name: 'tag1'}] or ['tag1', 'tag2'] (strings are auto-wrapped)", itemType: "object" },
|
|
30
|
+
{ name: "owners", type: "array", required: false, description: "Harness-native mode only. Updated owners — each entry is {type: \"USER\", id or email} or {type: \"GROUP\", identifier}", itemType: "object" },
|
|
12
31
|
{ name: "rolloutStatus", type: "object", required: false, description: "Rollout status — provide as {id: '<uuid>'} (use fme_rollout_status to discover valid IDs)" },
|
|
13
32
|
],
|
|
14
33
|
};
|
|
15
34
|
const fmeFeatureFlagCreateSchema = {
|
|
16
|
-
description: "Create a new feature flag (
|
|
35
|
+
description: "Create a new feature flag. Legacy mode (workspace_id + traffic_type_id): name required, description optional — tags/owners not supported on create (the Split API has no create-time support; use harness_update after creation). Harness-native mode (org_id+project_id): name and trafficType required, description/tags/owners optional.",
|
|
17
36
|
fields: [
|
|
18
|
-
{ name: "name", type: "string", required: true, description: "Feature flag name (must be unique within the workspace)" },
|
|
37
|
+
{ name: "name", type: "string", required: true, description: "Feature flag name (must be unique within the workspace/project)" },
|
|
19
38
|
{ name: "description", type: "string", required: false, description: "Optional description of the feature flag" },
|
|
39
|
+
{ name: "trafficType", type: "string", required: false, description: "Traffic type name. Required in Harness-native (org_id+project_id) mode; also accepted as top-level traffic_type_id or traffic_type. Ignored in legacy mode (pass traffic_type_id as a path param there instead)" },
|
|
40
|
+
{ name: "tags", type: "array", required: false, description: "Harness-native mode only. Each entry is {name: string}; bare strings are accepted and auto-wrapped", itemType: "object" },
|
|
41
|
+
{ name: "owners", type: "array", required: false, description: "Harness-native mode only. Each entry is {type: \"USER\", id or email} or {type: \"GROUP\", identifier}", itemType: "object" },
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
const fmeFeatureFlagKillRestoreReallocateSchema = {
|
|
45
|
+
description: "Optional comment and/or title recorded with the action. Both fields are accepted in legacy (workspace_id) and Harness-native (org_id+project_id) mode.",
|
|
46
|
+
fields: [
|
|
47
|
+
{ name: "comment", type: "string", required: false, description: "Optional comment explaining the change" },
|
|
48
|
+
{ name: "title", type: "string", required: false, description: "Optional short title for the change" },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
const fmeFeatureFlagArchiveSchema = {
|
|
52
|
+
description: "Optional comment recorded with the action.",
|
|
53
|
+
fields: [
|
|
54
|
+
{ name: "comment", type: "string", required: false, description: "Optional comment explaining the change" },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
const fmeEnvironmentCreateSchema = {
|
|
58
|
+
description: "Create an FME environment (Harness-native org_id+project_id only). name is required (max 15 characters). isProduction is optional (default false). production is accepted as an alias for isProduction.",
|
|
59
|
+
fields: [
|
|
60
|
+
{ name: "name", type: "string", required: true, description: "Environment name (unique in the project; max 15 characters)" },
|
|
61
|
+
{ name: "isProduction", type: "boolean", required: false, description: "Whether this is a production environment. Optional; defaults to false on the backend if omitted." },
|
|
62
|
+
{ name: "production", type: "boolean", required: false, description: "Alias for isProduction." },
|
|
20
63
|
],
|
|
21
64
|
};
|
|
65
|
+
const fmeEnvironmentUpdateSchema = {
|
|
66
|
+
description: "Partial environment update via JSON Merge Patch (RFC 7396), Harness-native only. name and isProduction are not clearable — omit to leave unchanged. production is accepted as an alias for isProduction.",
|
|
67
|
+
fields: [
|
|
68
|
+
{ name: "name", type: "string", required: false, description: "Updated name; omit to leave unchanged. Blank names are rejected. Not clearable." },
|
|
69
|
+
{ name: "isProduction", type: "boolean", required: false, description: "Updated production flag. Omit to leave unchanged; not clearable." },
|
|
70
|
+
{ name: "production", type: "boolean", required: false, description: "Alias for isProduction." },
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
function fmeEnvironmentProduction(body) {
|
|
74
|
+
if (!body)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (body.isProduction !== undefined && body.isProduction !== null)
|
|
77
|
+
return Boolean(body.isProduction);
|
|
78
|
+
if (body.production !== undefined && body.production !== null)
|
|
79
|
+
return Boolean(body.production);
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/** MCP never had workspace_id get/create/update/delete for environments (#806 list-only). */
|
|
83
|
+
function resolveNativeOnlyEnvironmentRoute(input, operation, opts = {}) {
|
|
84
|
+
const mode = resolveFmeDualMode(input, "fme_environment");
|
|
85
|
+
if (mode.mode === "legacy") {
|
|
86
|
+
throw new Error(`fme_environment.${operation}: Harness-native (org_id/project_id) only — MCP never supported workspace_id for this operation (list remains dual-mode).`);
|
|
87
|
+
}
|
|
88
|
+
if (opts.collection) {
|
|
89
|
+
return {
|
|
90
|
+
path: "/fme/api/v4/environments",
|
|
91
|
+
product: "harness",
|
|
92
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_environment"));
|
|
96
|
+
return {
|
|
97
|
+
path: `/fme/api/v4/environments/${environmentId}`,
|
|
98
|
+
product: "harness",
|
|
99
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
100
|
+
...(opts.mergePatch ? { headers: { "Content-Type": "application/merge-patch+json" } } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
22
103
|
const fmeFeatureFlagDefinitionCreateSchema = {
|
|
23
104
|
description: "Create a feature flag definition in a specific environment (initial treatments, rules, and default rule required)",
|
|
24
105
|
fields: [
|
|
@@ -29,6 +110,7 @@ const fmeFeatureFlagDefinitionCreateSchema = {
|
|
|
29
110
|
{ name: "baselineTreatment", type: "string", required: false, description: "The baseline (control) treatment for experimentation" },
|
|
30
111
|
{ name: "trafficAllocation", type: "number", required: false, description: "Percentage of traffic to include (0–100)" },
|
|
31
112
|
{ name: "comment", type: "string", required: false, description: "Comment describing the change" },
|
|
113
|
+
{ name: "title", type: "string", required: false, description: "Harness-native mode only. Optional definition title." },
|
|
32
114
|
],
|
|
33
115
|
};
|
|
34
116
|
const fmeFeatureFlagDefinitionUpdateSchema = {
|
|
@@ -41,6 +123,7 @@ const fmeFeatureFlagDefinitionUpdateSchema = {
|
|
|
41
123
|
{ name: "defaultRule", type: "array", required: false, description: "Default rule buckets (treatment/size pairs) applied when no targeting rules match", itemType: "object" },
|
|
42
124
|
{ name: "trafficAllocation", type: "number", required: false, description: "Percentage of traffic to include (0–100)" },
|
|
43
125
|
{ name: "comment", type: "string", required: false, description: "Comment describing the change" },
|
|
126
|
+
{ name: "title", type: "string", required: false, description: "Harness-native mode only. Optional definition title." },
|
|
44
127
|
],
|
|
45
128
|
};
|
|
46
129
|
const fmeRbsCreateSchema = {
|
|
@@ -50,6 +133,25 @@ const fmeRbsCreateSchema = {
|
|
|
50
133
|
{ name: "description", type: "string", required: false, description: "Optional segment description" },
|
|
51
134
|
],
|
|
52
135
|
};
|
|
136
|
+
const fmeSegmentCreateSchema = {
|
|
137
|
+
description: "Create a new segment. name, trafficType, and type are required; description/tags/owners are optional.",
|
|
138
|
+
fields: [
|
|
139
|
+
{ name: "name", type: "string", required: true, description: "Segment name (must be unique within the project)" },
|
|
140
|
+
{ name: "type", type: "string", required: true, description: "Segment kind. Must be one of: \"standard\" | \"rule_based\" | \"large\"." },
|
|
141
|
+
{ name: "description", type: "string", required: false, description: "Optional description of the segment" },
|
|
142
|
+
{ name: "trafficType", type: "string", required: true, description: "Traffic type name" },
|
|
143
|
+
{ name: "tags", type: "array", required: false, description: "Each entry is {name: string}; bare strings are accepted and auto-wrapped", itemType: "object" },
|
|
144
|
+
{ name: "owners", type: "array", required: false, description: "Each entry is {type: \"USER\", id or email} or {type: \"GROUP\", identifier}", itemType: "object" },
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
const fmeSegmentDefinitionCreateSchema = {
|
|
148
|
+
description: "Create a segment definition in an environment. Omit the body (or send {}) to create an empty/default shell.",
|
|
149
|
+
fields: [{ name: "description", type: "string", required: false, description: "Optional description for the definition" }],
|
|
150
|
+
};
|
|
151
|
+
const fmeSegmentDefinitionUpdateSchema = {
|
|
152
|
+
description: "Update a segment definition via JSON Merge Patch (RFC 7396). description is the only mutable field: omit it to leave unchanged, pass null to clear it, or a string to set it.",
|
|
153
|
+
fields: [{ name: "description", type: "string", required: false, description: "Omit to keep the current value, null to clear it, or a string to set it" }],
|
|
154
|
+
};
|
|
53
155
|
const fmeRbsUpdateDefinitionSchema = {
|
|
54
156
|
description: "Update a rule-based segment definition in an environment. Rules use: {condition: {combiner: 'AND', matchers: [{type, attribute, ...}]}}. Matcher types: IN_LIST_STRING (strings:[]), GREATER_THAN_OR_EQUAL_NUMBER (number:N), LESS_THAN_OR_EQUAL_NUMBER (number:N), BETWEEN_NUMBER (between:{from,to}), BOOLEAN (bool:true/false), ON_DATE (date:ms), IN_SPLIT (depends:{splitName,treatment}). Combiner values: AND, OR.",
|
|
55
157
|
fields: [
|
|
@@ -92,10 +194,35 @@ const fmeRbsChangeRequestSchema = {
|
|
|
92
194
|
},
|
|
93
195
|
],
|
|
94
196
|
};
|
|
197
|
+
function resolveNativeOnlyDefinitionRoute(input, operation, opts = {}) {
|
|
198
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag_definition");
|
|
199
|
+
if (mode.mode === "legacy") {
|
|
200
|
+
throw new Error(`fme_feature_flag_definition.${operation}: Harness-native (org_id/project_id) only — pass org_id+project_id instead of workspace_id.`);
|
|
201
|
+
}
|
|
202
|
+
if (opts.collection) {
|
|
203
|
+
requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag_definition");
|
|
204
|
+
return {
|
|
205
|
+
path: "/fme/api/v4/feature-flag-definitions",
|
|
206
|
+
product: "harness",
|
|
207
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag_definition"));
|
|
211
|
+
requireFmeIdentifier(input, "environment_id", "fme_feature_flag_definition");
|
|
212
|
+
return {
|
|
213
|
+
path: `/fme/api/v4/feature-flag-definitions/${flagName}${opts.suffix ?? ""}`,
|
|
214
|
+
product: "harness",
|
|
215
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
95
218
|
export const featureFlagsToolset = {
|
|
96
219
|
name: "feature-flags",
|
|
97
220
|
displayName: "Feature Management & Experimentation",
|
|
98
|
-
description: "Harness FME — feature flags,
|
|
221
|
+
description: "Harness FME — feature flags, segments, environments, and rollout statuses. " +
|
|
222
|
+
"Two mutually exclusive scoping modes: legacy workspace_id (Split.io API) or " +
|
|
223
|
+
"Harness-native org_id+project_id. If you already have org_id and project_id, use " +
|
|
224
|
+
"them directly and skip fme_workspace entirely — org/project fully identify scope " +
|
|
225
|
+
"on their own; there is no need to look up a workspace first.",
|
|
99
226
|
resources: [
|
|
100
227
|
// ── FME Resources (Split.io API at https://api.split.io) ───────────
|
|
101
228
|
// These use account scope to avoid injecting orgIdentifier/projectIdentifier
|
|
@@ -104,9 +231,14 @@ export const featureFlagsToolset = {
|
|
|
104
231
|
{
|
|
105
232
|
resourceType: "fme_workspace",
|
|
106
233
|
displayName: "FME Workspace",
|
|
107
|
-
description: "Feature Management workspace
|
|
234
|
+
description: "Feature Management workspace — legacy Split.io concept only, used solely to discover a " +
|
|
235
|
+
"workspace_id for other fme_* resources' deprecated legacy calls. Not used by Harness-native " +
|
|
236
|
+
"calls at all: if you already have org_id and project_id, skip this resource entirely and " +
|
|
237
|
+
"pass them directly to the fme_* resource you actually want. Supports list with pagination " +
|
|
238
|
+
"(offset/size, default 20, max 1000).",
|
|
108
239
|
toolset: "feature-flags",
|
|
109
240
|
scope: "account",
|
|
241
|
+
scopeOptional: true,
|
|
110
242
|
identifierFields: ["workspace_id"],
|
|
111
243
|
product: "fme",
|
|
112
244
|
listFilterFields: [
|
|
@@ -116,6 +248,13 @@ export const featureFlagsToolset = {
|
|
|
116
248
|
list: {
|
|
117
249
|
method: "GET",
|
|
118
250
|
path: "/internal/api/v2/workspaces",
|
|
251
|
+
routeResolver: (input) => {
|
|
252
|
+
if (isFmeHarnessNativeSelected(input, "fme_workspace.list")) {
|
|
253
|
+
throw new Error("fme_workspace: this resource has no Harness-native equivalent — it exists only to discover workspace_id values for the deprecated legacy contract.");
|
|
254
|
+
}
|
|
255
|
+
logFmeDeprecation("[DEPRECATION] fme_workspace: this resource and the workspace_id contract it supports are deprecated.");
|
|
256
|
+
return { path: "/internal/api/v2/workspaces" };
|
|
257
|
+
},
|
|
119
258
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
120
259
|
queryParams: {
|
|
121
260
|
offset: "offset",
|
|
@@ -129,37 +268,104 @@ export const featureFlagsToolset = {
|
|
|
129
268
|
{
|
|
130
269
|
resourceType: "fme_environment",
|
|
131
270
|
displayName: "FME Environment",
|
|
132
|
-
description: "Feature Management environment.
|
|
271
|
+
description: "Feature Management environment. Dual-mode list (workspace_id or org_id+project_id). get/create/update/delete are Harness-native only. Native create/update use isProduction (production accepted as an alias). Native PATCH is JSON Merge Patch; name and isProduction are not clearable. Name max 15 characters. Delete returns 400 hasDependents while SDK API keys (always created with a new env), flags, or segments remain.",
|
|
133
272
|
toolset: "feature-flags",
|
|
134
273
|
scope: "account",
|
|
274
|
+
scopeOptional: true,
|
|
135
275
|
identifierFields: ["workspace_id", "environment_id"],
|
|
136
276
|
product: "fme",
|
|
137
|
-
baseUrlOverride: "fme",
|
|
138
277
|
listFilterFields: [
|
|
139
|
-
{ name: "workspace_id", description: "FME workspace ID (get from harness_list resource_type=fme_workspace)
|
|
278
|
+
{ name: "workspace_id", description: "FME workspace ID (get from harness_list resource_type=fme_workspace). Deprecated — omit and pass org_id+project_id instead for Harness-native scoping." },
|
|
279
|
+
{ name: "offset", description: "Harness-native pagination offset (default 0)", type: "number" },
|
|
280
|
+
{ name: "limit", description: "Harness-native page size (default 100, max 100)", type: "number" },
|
|
140
281
|
],
|
|
141
282
|
operations: {
|
|
142
283
|
list: {
|
|
143
284
|
method: "GET",
|
|
144
|
-
path: "
|
|
285
|
+
path: "",
|
|
286
|
+
routeResolver: (input) => {
|
|
287
|
+
const mode = resolveFmeDualMode(input, "fme_environment");
|
|
288
|
+
if (mode.mode === "legacy") {
|
|
289
|
+
return { path: `/internal/api/v2/environments/ws/${encodeURIComponent(mode.workspaceId)}` };
|
|
290
|
+
}
|
|
291
|
+
return { path: "/fme/api/v4/environments", product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
292
|
+
},
|
|
145
293
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
146
|
-
|
|
294
|
+
queryParams: { offset: "offset", size: "limit", limit: "limit" },
|
|
295
|
+
responseExtractor: fmeV4PaginatedListExtract,
|
|
296
|
+
description: "List FME environments for a workspace (legacy) or org_id+project_id project (Harness-native). Native envelope {data, limit, offset, totalCount} is promoted to items/total; harness_list size maps to limit.",
|
|
297
|
+
},
|
|
298
|
+
get: {
|
|
299
|
+
method: "GET",
|
|
300
|
+
path: "",
|
|
301
|
+
routeResolver: (input) => resolveNativeOnlyEnvironmentRoute(input, "get"),
|
|
302
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
303
|
+
responseExtractor: passthrough,
|
|
304
|
+
description: "Get a single environment by environment_id (UUID from list). Harness-native only (org_id+project_id). MCP never supported workspace_id get.",
|
|
305
|
+
},
|
|
306
|
+
create: {
|
|
307
|
+
method: "POST",
|
|
308
|
+
path: "",
|
|
309
|
+
routeResolver: (input) => resolveNativeOnlyEnvironmentRoute(input, "create", { collection: true }),
|
|
310
|
+
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
311
|
+
skipScopeBodyInjection: true,
|
|
312
|
+
bodyBuilder: (input) => {
|
|
313
|
+
const body = input.body;
|
|
314
|
+
const production = fmeEnvironmentProduction(body);
|
|
315
|
+
return {
|
|
316
|
+
name: body?.name,
|
|
317
|
+
...(production !== undefined ? { isProduction: production } : {}),
|
|
318
|
+
};
|
|
319
|
+
},
|
|
147
320
|
responseExtractor: passthrough,
|
|
148
|
-
|
|
321
|
+
bodySchema: fmeEnvironmentCreateSchema,
|
|
322
|
+
description: "Create an environment (Harness-native only). Body requires name (max 15 characters). Optional isProduction (CreateEnvironmentRequest). NG scope is not injected into the JSON.",
|
|
323
|
+
},
|
|
324
|
+
update: {
|
|
325
|
+
method: "PATCH",
|
|
326
|
+
path: "",
|
|
327
|
+
routeResolver: (input) => resolveNativeOnlyEnvironmentRoute(input, "update", { mergePatch: true }),
|
|
328
|
+
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
329
|
+
skipScopeBodyInjection: true,
|
|
330
|
+
bodyBuilder: (input) => {
|
|
331
|
+
const body = input.body;
|
|
332
|
+
if (!body)
|
|
333
|
+
return {};
|
|
334
|
+
const production = fmeEnvironmentProduction(body);
|
|
335
|
+
return {
|
|
336
|
+
...(typeof body.name === "string" ? { name: body.name } : {}),
|
|
337
|
+
...(production !== undefined ? { isProduction: production } : {}),
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
responseExtractor: passthrough,
|
|
341
|
+
bodySchema: fmeEnvironmentUpdateSchema,
|
|
342
|
+
description: "Update an environment by environment_id (Harness-native only). JSON Merge Patch on name and isProduction (not clearable).",
|
|
343
|
+
},
|
|
344
|
+
delete: {
|
|
345
|
+
method: "DELETE",
|
|
346
|
+
path: "",
|
|
347
|
+
routeResolver: (input) => resolveNativeOnlyEnvironmentRoute(input, "delete"),
|
|
348
|
+
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
349
|
+
responseExtractor: passthrough,
|
|
350
|
+
description: "Delete (archive) an environment by environment_id (Harness-native only). Returns 400 hasDependents while SDK API keys, flags, or segments still target it. Create via EnvironmentStarterKit always provisions client/server API keys, so a brand-new environment cannot be deleted until those keys are removed.",
|
|
149
351
|
},
|
|
150
352
|
},
|
|
151
353
|
},
|
|
152
354
|
{
|
|
153
355
|
resourceType: "fme_feature_flag",
|
|
154
356
|
displayName: "FME Feature Flag",
|
|
155
|
-
description: "Feature flag
|
|
357
|
+
description: "Feature flag. Dual-mode scoping: pass org_id+project_id (Harness-native, preferred — no " +
|
|
358
|
+
"workspace lookup needed) or the deprecated workspace_id (Split.io API). Both modes support " +
|
|
359
|
+
"list/get/create/delete/update/kill/restore/reallocate/archive/unarchive. List supports " +
|
|
360
|
+
"filtering (name, tags, rollout_status_id) and pagination (offset/size, default 20, max 50).",
|
|
156
361
|
toolset: "feature-flags",
|
|
157
362
|
scope: "account",
|
|
363
|
+
scopeOptional: true,
|
|
158
364
|
identifierFields: ["workspace_id", "feature_flag_name"],
|
|
159
365
|
product: "fme",
|
|
160
366
|
deepLinkTemplate: "/ng/account/{accountId}/module/fme/orgs/{orgIdentifier}/projects/{projectIdentifier}/setup/resources/targets/{trafficTypeId}/splits/{id}",
|
|
161
367
|
listFilterFields: [
|
|
162
|
-
{ name: "workspace_id", description: "FME workspace ID (get from harness_list resource_type=fme_workspace)
|
|
368
|
+
{ name: "workspace_id", description: "FME workspace ID (get from harness_list resource_type=fme_workspace). Deprecated — omit and pass org_id+project_id instead for Harness-native scoping." },
|
|
163
369
|
{ name: "offset", description: "Pagination offset for FME feature flags", type: "number" },
|
|
164
370
|
{ name: "rollout_status_id", description: "Filter by rollout status UUID (use fme_rollout_status to discover valid IDs)", type: "string" },
|
|
165
371
|
{ name: "name", description: "Filter flags by name (partial match)", type: "string" },
|
|
@@ -168,9 +374,15 @@ export const featureFlagsToolset = {
|
|
|
168
374
|
operations: {
|
|
169
375
|
list: {
|
|
170
376
|
method: "GET",
|
|
171
|
-
path: "
|
|
377
|
+
path: "",
|
|
378
|
+
routeResolver: (input) => {
|
|
379
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
380
|
+
if (mode.mode === "legacy") {
|
|
381
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}` };
|
|
382
|
+
}
|
|
383
|
+
return { path: "/fme/api/v4/feature-flags", product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
384
|
+
},
|
|
172
385
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
173
|
-
pathParams: { workspace_id: "wsId" },
|
|
174
386
|
queryParams: {
|
|
175
387
|
offset: "offset",
|
|
176
388
|
size: "limit",
|
|
@@ -179,23 +391,54 @@ export const featureFlagsToolset = {
|
|
|
179
391
|
tags: "tag",
|
|
180
392
|
},
|
|
181
393
|
responseExtractor: fmeListExtract,
|
|
182
|
-
description: "List feature flags
|
|
394
|
+
description: "List feature flags by workspace_id (legacy, deprecated) or org_id+project_id (Harness-native), with filtering and pagination (offset and size params, max 50).",
|
|
183
395
|
},
|
|
184
396
|
get: {
|
|
185
397
|
method: "GET",
|
|
186
|
-
path: "
|
|
398
|
+
path: "",
|
|
399
|
+
routeResolver: (input) => {
|
|
400
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
401
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
402
|
+
if (mode.mode === "legacy") {
|
|
403
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}` };
|
|
404
|
+
}
|
|
405
|
+
return { path: `/fme/api/v4/feature-flags/${flagName}`, product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
406
|
+
},
|
|
187
407
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
188
|
-
pathParams: { workspace_id: "wsId", feature_flag_name: "featureFlagName" },
|
|
189
408
|
responseExtractor: fmeGetExtract,
|
|
190
|
-
description: "Get a specific feature flag's metadata without requiring an environment",
|
|
409
|
+
description: "Get a specific feature flag's metadata without requiring an environment (legacy: workspace_id; Harness-native: org_id+project_id).",
|
|
191
410
|
},
|
|
192
411
|
create: {
|
|
193
412
|
method: "POST",
|
|
194
413
|
path: "/internal/api/v2/splits/ws/{wsId}/trafficTypes/{trafficTypeId}",
|
|
414
|
+
routeResolver: (input) => {
|
|
415
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
416
|
+
if (mode.mode === "harness_native") {
|
|
417
|
+
return { path: "/fme/api/v4/feature-flags", product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
418
|
+
}
|
|
419
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/trafficTypes/${encodeURIComponent(requireFmeIdentifier(input, "traffic_type_id", "fme_feature_flag"))}` };
|
|
420
|
+
},
|
|
195
421
|
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
196
422
|
pathParams: { workspace_id: "wsId", traffic_type_id: "trafficTypeId" },
|
|
197
423
|
bodyBuilder: (input) => {
|
|
198
424
|
const body = input.body;
|
|
425
|
+
if (isFmeHarnessNativeSelected(input, "fme_feature_flag")) {
|
|
426
|
+
const trafficType = (body?.trafficType ?? input.traffic_type_id ?? input.traffic_type ?? body?.traffic_type);
|
|
427
|
+
if (!trafficType) {
|
|
428
|
+
throw new Error('fme_feature_flag.create: "trafficType" is required in Harness-native mode — pass body.trafficType, traffic_type_id, or traffic_type.');
|
|
429
|
+
}
|
|
430
|
+
const name = body?.name ?? input.name;
|
|
431
|
+
if (!name) {
|
|
432
|
+
throw new Error('fme_feature_flag.create: "name" is required in Harness-native mode — pass body.name.');
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
name,
|
|
436
|
+
trafficType,
|
|
437
|
+
...(body?.description !== undefined ? { description: body.description } : {}),
|
|
438
|
+
...(body?.tags !== undefined ? { tags: normalizeFmeTags(body.tags) } : {}),
|
|
439
|
+
...(body?.owners !== undefined ? { owners: body.owners } : {}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
199
442
|
return {
|
|
200
443
|
name: body?.name ?? input.name,
|
|
201
444
|
...(body?.description || input.description ? { description: body?.description ?? input.description } : {}),
|
|
@@ -203,23 +446,56 @@ export const featureFlagsToolset = {
|
|
|
203
446
|
},
|
|
204
447
|
responseExtractor: passthrough,
|
|
205
448
|
bodySchema: fmeFeatureFlagCreateSchema,
|
|
206
|
-
description: "Create a feature flag
|
|
449
|
+
description: "Create a feature flag. Legacy mode (workspace_id): requires workspace_id + traffic_type_id (get from fme_traffic_type); body: name, optional description (no tags/owners — use harness_update after creation). Harness-native mode (org_id+project_id): body requires name + trafficType (or pass traffic_type_id / traffic_type at top level), optional description/tags/owners.",
|
|
207
450
|
},
|
|
208
451
|
delete: {
|
|
209
452
|
method: "DELETE",
|
|
210
|
-
path: "
|
|
453
|
+
path: "",
|
|
454
|
+
routeResolver: (input) => {
|
|
455
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
456
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
457
|
+
if (mode.mode === "legacy") {
|
|
458
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}` };
|
|
459
|
+
}
|
|
460
|
+
return { path: `/fme/api/v4/feature-flags/${flagName}`, product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
461
|
+
},
|
|
211
462
|
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
212
|
-
pathParams: { workspace_id: "wsId", feature_flag_name: "featureFlagName" },
|
|
213
463
|
responseExtractor: passthrough,
|
|
214
|
-
description: "Delete a feature flag from a workspace",
|
|
464
|
+
description: "Delete a feature flag from a workspace (legacy) or org_id+project_id-scoped project (Harness-native)",
|
|
215
465
|
},
|
|
216
466
|
update: {
|
|
217
467
|
method: "PATCH",
|
|
218
468
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}",
|
|
469
|
+
routeResolver: (input) => {
|
|
470
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
471
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
472
|
+
if (mode.mode === "harness_native") {
|
|
473
|
+
return {
|
|
474
|
+
path: `/fme/api/v4/feature-flags/${flagName}`,
|
|
475
|
+
product: "harness",
|
|
476
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
477
|
+
headers: { "Content-Type": "application/merge-patch+json" },
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}` };
|
|
481
|
+
},
|
|
219
482
|
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
220
483
|
pathParams: { workspace_id: "wsId", feature_flag_name: "featureFlagName" },
|
|
221
484
|
bodyBuilder: (input) => {
|
|
222
485
|
const body = input.body;
|
|
486
|
+
if (isFmeHarnessNativeSelected(input, "fme_feature_flag")) {
|
|
487
|
+
// JSON Merge Patch (RFC 7396): omit a field to leave it unchanged, set it to update,
|
|
488
|
+
// or (for description/tags/owners) set it to null/[] to clear it. rolloutStatus is
|
|
489
|
+
// set-only — the backend rejects an explicit null for it.
|
|
490
|
+
if (!body)
|
|
491
|
+
return {};
|
|
492
|
+
return {
|
|
493
|
+
...(body.description !== undefined ? { description: body.description } : {}),
|
|
494
|
+
...(body.tags !== undefined ? { tags: body.tags === null ? null : normalizeFmeTags(body.tags) } : {}),
|
|
495
|
+
...(body.owners !== undefined ? { owners: body.owners } : {}),
|
|
496
|
+
...(body.rolloutStatus !== undefined ? { rolloutStatus: body.rolloutStatus } : {}),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
223
499
|
if (!body)
|
|
224
500
|
return [];
|
|
225
501
|
const ops = [];
|
|
@@ -242,7 +518,7 @@ export const featureFlagsToolset = {
|
|
|
242
518
|
return ops;
|
|
243
519
|
},
|
|
244
520
|
responseExtractor: passthrough,
|
|
245
|
-
description: "Update a feature flag's metadata (description
|
|
521
|
+
description: "Update a feature flag's metadata. Legacy mode (workspace_id): description/tags/rolloutStatus via JSON Patch (RFC 6902) — provide fields directly, they're converted to patch ops automatically. Harness-native mode (org_id+project_id): description/tags/owners/rolloutStatus via JSON Merge Patch (RFC 7396) — omit a field to leave it unchanged, set it to update, or set description/tags/owners to null (or [] for tags/owners) to clear it; rolloutStatus is set-only (no null).",
|
|
246
522
|
bodySchema: fmeFeatureFlagUpdateSchema,
|
|
247
523
|
},
|
|
248
524
|
},
|
|
@@ -250,172 +526,409 @@ export const featureFlagsToolset = {
|
|
|
250
526
|
kill: {
|
|
251
527
|
method: "PUT",
|
|
252
528
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/environments/{environmentId}/kill",
|
|
529
|
+
methodBuilder: (input) => (isFmeHarnessNativeSelected(input, "fme_feature_flag") ? "POST" : "PUT"),
|
|
530
|
+
routeResolver: (input) => {
|
|
531
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
532
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
533
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_feature_flag"));
|
|
534
|
+
if (mode.mode === "harness_native") {
|
|
535
|
+
return {
|
|
536
|
+
path: `/fme/api/v4/feature-flag-definitions/${flagName}/kill`,
|
|
537
|
+
product: "harness",
|
|
538
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/environments/${environmentId}/kill` };
|
|
542
|
+
},
|
|
253
543
|
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
254
544
|
pathParams: {
|
|
255
545
|
workspace_id: "wsId",
|
|
256
546
|
feature_flag_name: "featureFlagName",
|
|
257
547
|
environment_id: "environmentId",
|
|
258
548
|
},
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
549
|
+
queryParams: { environment_id: "environment_id" },
|
|
550
|
+
bodyBuilder: (input) => {
|
|
551
|
+
const body = input.body ?? {};
|
|
552
|
+
return {
|
|
553
|
+
...(body.comment !== undefined ? { comment: body.comment } : {}),
|
|
554
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
555
|
+
};
|
|
265
556
|
},
|
|
557
|
+
responseExtractor: fmeActionExtract,
|
|
558
|
+
actionDescription: "Kill (turn off) a feature flag in a specific environment. Requires feature_flag_name and environment_id, plus workspace_id (legacy) or org_id+project_id (Harness-native). Optional comment/title recorded with the change.",
|
|
559
|
+
bodySchema: fmeFeatureFlagKillRestoreReallocateSchema,
|
|
266
560
|
},
|
|
267
561
|
restore: {
|
|
268
562
|
method: "PUT",
|
|
269
563
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/environments/{environmentId}/restore",
|
|
564
|
+
methodBuilder: (input) => (isFmeHarnessNativeSelected(input, "fme_feature_flag") ? "POST" : "PUT"),
|
|
565
|
+
routeResolver: (input) => {
|
|
566
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
567
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
568
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_feature_flag"));
|
|
569
|
+
if (mode.mode === "harness_native") {
|
|
570
|
+
return {
|
|
571
|
+
path: `/fme/api/v4/feature-flag-definitions/${flagName}/restore`,
|
|
572
|
+
product: "harness",
|
|
573
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/environments/${environmentId}/restore` };
|
|
577
|
+
},
|
|
270
578
|
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
271
579
|
pathParams: {
|
|
272
580
|
workspace_id: "wsId",
|
|
273
581
|
feature_flag_name: "featureFlagName",
|
|
274
582
|
environment_id: "environmentId",
|
|
275
583
|
},
|
|
276
|
-
|
|
584
|
+
queryParams: { environment_id: "environment_id" },
|
|
585
|
+
bodyBuilder: (input) => {
|
|
586
|
+
const body = input.body ?? {};
|
|
587
|
+
return {
|
|
588
|
+
...(body.comment !== undefined ? { comment: body.comment } : {}),
|
|
589
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
590
|
+
};
|
|
591
|
+
},
|
|
277
592
|
responseExtractor: fmeActionExtract,
|
|
278
|
-
actionDescription: "Restore (re-enable) a killed feature flag in a specific environment. Requires
|
|
279
|
-
bodySchema:
|
|
280
|
-
|
|
281
|
-
|
|
593
|
+
actionDescription: "Restore (re-enable) a killed feature flag in a specific environment. Requires feature_flag_name and environment_id, plus workspace_id (legacy) or org_id+project_id (Harness-native). Optional comment/title recorded with the change.",
|
|
594
|
+
bodySchema: fmeFeatureFlagKillRestoreReallocateSchema,
|
|
595
|
+
},
|
|
596
|
+
reallocate: {
|
|
597
|
+
method: "POST",
|
|
598
|
+
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/environments/{environmentId}/reallocate",
|
|
599
|
+
routeResolver: (input) => {
|
|
600
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
601
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
602
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_feature_flag"));
|
|
603
|
+
if (mode.mode === "harness_native") {
|
|
604
|
+
return {
|
|
605
|
+
path: `/fme/api/v4/feature-flag-definitions/${flagName}/reallocate`,
|
|
606
|
+
product: "harness",
|
|
607
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/environments/${environmentId}/reallocate` };
|
|
611
|
+
},
|
|
612
|
+
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
613
|
+
pathParams: {
|
|
614
|
+
workspace_id: "wsId",
|
|
615
|
+
feature_flag_name: "featureFlagName",
|
|
616
|
+
environment_id: "environmentId",
|
|
617
|
+
},
|
|
618
|
+
queryParams: { environment_id: "environment_id" },
|
|
619
|
+
bodyBuilder: (input) => {
|
|
620
|
+
const body = input.body ?? {};
|
|
621
|
+
return {
|
|
622
|
+
...(body.comment !== undefined ? { comment: body.comment } : {}),
|
|
623
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
624
|
+
};
|
|
282
625
|
},
|
|
626
|
+
responseExtractor: fmeActionExtract,
|
|
627
|
+
actionDescription: "Reallocate traffic across treatments for a feature flag in a specific environment, without a full targeting-rules update. Requires feature_flag_name and environment_id, plus workspace_id (legacy) or org_id+project_id (Harness-native). Optional comment/title recorded with the change.",
|
|
628
|
+
bodySchema: fmeFeatureFlagKillRestoreReallocateSchema,
|
|
283
629
|
},
|
|
284
630
|
archive: {
|
|
285
631
|
method: "POST",
|
|
286
632
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/archive",
|
|
633
|
+
routeResolver: (input) => {
|
|
634
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
635
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
636
|
+
if (mode.mode === "harness_native") {
|
|
637
|
+
return {
|
|
638
|
+
path: `/fme/api/v4/feature-flags/${flagName}/archive`,
|
|
639
|
+
product: "harness",
|
|
640
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/archive` };
|
|
644
|
+
},
|
|
287
645
|
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
288
646
|
pathParams: { workspace_id: "wsId", feature_flag_name: "featureFlagName" },
|
|
289
|
-
bodyBuilder: () =>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
bodySchema: {
|
|
293
|
-
description: "No body required for this action.",
|
|
294
|
-
fields: [],
|
|
647
|
+
bodyBuilder: (input) => {
|
|
648
|
+
const body = input.body ?? {};
|
|
649
|
+
return body.comment !== undefined ? { comment: body.comment } : {};
|
|
295
650
|
},
|
|
651
|
+
responseExtractor: fmeActionExtract,
|
|
652
|
+
actionDescription: "Archive a feature flag. Requires feature_flag_name, plus workspace_id (legacy) or org_id+project_id (Harness-native). Subject to OPA policy checks (409 on failure). Optional comment recorded with the change.",
|
|
653
|
+
bodySchema: fmeFeatureFlagArchiveSchema,
|
|
296
654
|
},
|
|
297
655
|
unarchive: {
|
|
298
656
|
method: "POST",
|
|
299
657
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/unarchive",
|
|
658
|
+
routeResolver: (input) => {
|
|
659
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag");
|
|
660
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag"));
|
|
661
|
+
if (mode.mode === "harness_native") {
|
|
662
|
+
return {
|
|
663
|
+
path: `/fme/api/v4/feature-flags/${flagName}/unarchive`,
|
|
664
|
+
product: "harness",
|
|
665
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/unarchive` };
|
|
669
|
+
},
|
|
300
670
|
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
301
671
|
pathParams: { workspace_id: "wsId", feature_flag_name: "featureFlagName" },
|
|
302
|
-
bodyBuilder: () =>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
bodySchema: {
|
|
306
|
-
description: "No body required for this action.",
|
|
307
|
-
fields: [],
|
|
672
|
+
bodyBuilder: (input) => {
|
|
673
|
+
const body = input.body ?? {};
|
|
674
|
+
return body.comment !== undefined ? { comment: body.comment } : {};
|
|
308
675
|
},
|
|
676
|
+
responseExtractor: fmeActionExtract,
|
|
677
|
+
actionDescription: "Unarchive a previously archived feature flag. Requires feature_flag_name, plus workspace_id (legacy) or org_id+project_id (Harness-native). Returns 409 if the flag has dependent objects. Optional comment recorded with the change.",
|
|
678
|
+
bodySchema: fmeFeatureFlagArchiveSchema,
|
|
309
679
|
},
|
|
310
680
|
},
|
|
311
681
|
},
|
|
312
682
|
{
|
|
313
683
|
resourceType: "fme_feature_flag_definition",
|
|
314
684
|
displayName: "FME Feature Flag Definition",
|
|
315
|
-
description: "Detailed definition of a feature flag in a specific environment
|
|
685
|
+
description: "Detailed definition of a feature flag in a specific environment: treatments, rules, targeting, and traffic allocation. Create requires treatments, defaultTreatment, and defaultRule. Get/create/update: pass org_id+project_id (preferred) or the deprecated workspace_id. List/delete/kill/restore/reallocate: org_id+project_id only. Native list requires feature_flag_name and uses offset/limit (max 100); it does not take environment_id. Other ops require environment_id. Kill/restore/reallocate are the same actions as on fme_feature_flag; either resource works.",
|
|
316
686
|
toolset: "feature-flags",
|
|
317
687
|
scope: "account",
|
|
688
|
+
scopeOptional: true,
|
|
318
689
|
identifierFields: ["workspace_id", "environment_id", "feature_flag_name"],
|
|
319
690
|
product: "fme",
|
|
691
|
+
listFilterFields: [
|
|
692
|
+
{ name: "feature_flag_name", description: "Feature flag name whose definitions to list. Required.", required: true },
|
|
693
|
+
{ name: "offset", description: "Pagination offset (default 0)", type: "number" },
|
|
694
|
+
{ name: "limit", description: "Page size (default 100, max 100)", type: "number" },
|
|
695
|
+
],
|
|
320
696
|
operations: {
|
|
697
|
+
list: {
|
|
698
|
+
method: "GET",
|
|
699
|
+
path: "",
|
|
700
|
+
routeResolver: (input) => resolveNativeOnlyDefinitionRoute(input, "list", { collection: true }),
|
|
701
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
702
|
+
queryParams: { feature_flag_name: "feature_flag_name", offset: "offset", limit: "limit" },
|
|
703
|
+
responseExtractor: passthrough,
|
|
704
|
+
description: "List feature flag definitions for a flag across environments (org_id+project_id only). Requires feature_flag_name. Pagination uses offset and limit (default 100, max 100). Do not pass environment_id.",
|
|
705
|
+
},
|
|
321
706
|
get: {
|
|
322
707
|
method: "GET",
|
|
323
708
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/environments/{environmentId}",
|
|
709
|
+
routeResolver: (input) => {
|
|
710
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag_definition");
|
|
711
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag_definition"));
|
|
712
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_feature_flag_definition"));
|
|
713
|
+
if (mode.mode === "harness_native") {
|
|
714
|
+
return { path: `/fme/api/v4/feature-flag-definitions/${flagName}`, product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
715
|
+
}
|
|
716
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/environments/${environmentId}` };
|
|
717
|
+
},
|
|
324
718
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
325
719
|
pathParams: {
|
|
326
720
|
workspace_id: "wsId",
|
|
327
721
|
feature_flag_name: "featureFlagName",
|
|
328
722
|
environment_id: "environmentId",
|
|
329
723
|
},
|
|
724
|
+
queryParams: { environment_id: "environment_id" },
|
|
330
725
|
responseExtractor: passthrough,
|
|
331
|
-
description: "Get feature flag definition in a specific environment (treatments, rules, default rule, traffic allocation)",
|
|
726
|
+
description: "Get a feature flag definition in a specific environment (treatments, rules, default rule, traffic allocation). Requires feature_flag_name and environment_id. Pass org_id+project_id (preferred) or deprecated workspace_id.",
|
|
332
727
|
},
|
|
333
728
|
create: {
|
|
334
729
|
method: "POST",
|
|
335
730
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/environments/{environmentId}",
|
|
731
|
+
routeResolver: (input) => {
|
|
732
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag_definition");
|
|
733
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag_definition"));
|
|
734
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_feature_flag_definition"));
|
|
735
|
+
if (mode.mode === "harness_native") {
|
|
736
|
+
return { path: `/fme/api/v4/feature-flag-definitions/${flagName}`, product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
737
|
+
}
|
|
738
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/environments/${environmentId}` };
|
|
739
|
+
},
|
|
336
740
|
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
337
741
|
pathParams: {
|
|
338
742
|
workspace_id: "wsId",
|
|
339
743
|
feature_flag_name: "featureFlagName",
|
|
340
744
|
environment_id: "environmentId",
|
|
341
745
|
},
|
|
746
|
+
queryParams: { environment_id: "environment_id" },
|
|
342
747
|
bodyBuilder: (input) => input.body,
|
|
343
748
|
responseExtractor: passthrough,
|
|
344
749
|
bodySchema: fmeFeatureFlagDefinitionCreateSchema,
|
|
345
|
-
description: "Create a feature flag definition in a specific environment. Requires treatments (array of treatment objects), defaultTreatment (string matching a treatment name), and defaultRule (array of bucket objects). Optional: rules, baselineTreatment, trafficAllocation, comment.",
|
|
750
|
+
description: "Create a feature flag definition in a specific environment. Requires treatments (array of treatment objects), defaultTreatment (string matching a treatment name), and defaultRule (array of bucket objects). Optional: rules, baselineTreatment, trafficAllocation, comment, title (Harness-native only). Requires feature_flag_name and environment_id. Pass org_id+project_id (preferred) or deprecated workspace_id.",
|
|
346
751
|
},
|
|
347
752
|
update: {
|
|
348
753
|
method: "PUT",
|
|
754
|
+
methodBuilder: (input) => (isFmeHarnessNativeSelected(input, "fme_feature_flag_definition") ? "PATCH" : "PUT"),
|
|
349
755
|
path: "/internal/api/v2/splits/ws/{wsId}/{featureFlagName}/environments/{environmentId}",
|
|
756
|
+
routeResolver: (input) => {
|
|
757
|
+
const mode = resolveFmeDualMode(input, "fme_feature_flag_definition");
|
|
758
|
+
const flagName = encodeURIComponent(requireFmeIdentifier(input, "feature_flag_name", "fme_feature_flag_definition"));
|
|
759
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_feature_flag_definition"));
|
|
760
|
+
if (mode.mode === "harness_native") {
|
|
761
|
+
return {
|
|
762
|
+
path: `/fme/api/v4/feature-flag-definitions/${flagName}`,
|
|
763
|
+
product: "harness",
|
|
764
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
765
|
+
headers: { "Content-Type": "application/merge-patch+json" },
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return { path: `/internal/api/v2/splits/ws/${encodeURIComponent(mode.workspaceId)}/${flagName}/environments/${environmentId}` };
|
|
769
|
+
},
|
|
350
770
|
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
351
771
|
pathParams: {
|
|
352
772
|
workspace_id: "wsId",
|
|
353
773
|
feature_flag_name: "featureFlagName",
|
|
354
774
|
environment_id: "environmentId",
|
|
355
775
|
},
|
|
776
|
+
queryParams: { environment_id: "environment_id" },
|
|
356
777
|
bodyBuilder: (input) => input.body,
|
|
357
778
|
responseExtractor: passthrough,
|
|
358
779
|
bodySchema: fmeFeatureFlagDefinitionUpdateSchema,
|
|
359
|
-
description: "Update a feature flag definition in a specific environment (treatments, rules, default rule, traffic allocation, baseline treatment)",
|
|
780
|
+
description: "Update a feature flag definition in a specific environment (treatments, rules, default rule, traffic allocation, baseline treatment). Requires feature_flag_name and environment_id. Pass org_id+project_id (preferred) or deprecated workspace_id. Native update is JSON Merge Patch: omit a field to leave it unchanged; treatments/rules/defaultRule are omit-to-keep but reject explicit null (not clearable). Legacy update is a full replace.",
|
|
781
|
+
},
|
|
782
|
+
delete: {
|
|
783
|
+
method: "DELETE",
|
|
784
|
+
path: "",
|
|
785
|
+
routeResolver: (input) => resolveNativeOnlyDefinitionRoute(input, "delete"),
|
|
786
|
+
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
787
|
+
queryParams: { environment_id: "environment_id" },
|
|
788
|
+
responseExtractor: passthrough,
|
|
789
|
+
description: "Delete a feature flag definition in an environment (org_id+project_id only). Requires feature_flag_name and environment_id.",
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
executeActions: {
|
|
793
|
+
kill: {
|
|
794
|
+
method: "POST",
|
|
795
|
+
path: "",
|
|
796
|
+
routeResolver: (input) => resolveNativeOnlyDefinitionRoute(input, "kill", { suffix: "/kill" }),
|
|
797
|
+
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
798
|
+
queryParams: { environment_id: "environment_id" },
|
|
799
|
+
bodyBuilder: (input) => {
|
|
800
|
+
const body = input.body ?? {};
|
|
801
|
+
return {
|
|
802
|
+
...(body.comment !== undefined ? { comment: body.comment } : {}),
|
|
803
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
804
|
+
};
|
|
805
|
+
},
|
|
806
|
+
responseExtractor: fmeActionExtract,
|
|
807
|
+
actionDescription: "Kill (turn off) a feature flag definition in an environment (org_id+project_id only). Requires feature_flag_name and environment_id. Optional body comment/title. Same action as fme_feature_flag kill.",
|
|
808
|
+
bodySchema: fmeFeatureFlagKillRestoreReallocateSchema,
|
|
809
|
+
},
|
|
810
|
+
restore: {
|
|
811
|
+
method: "POST",
|
|
812
|
+
path: "",
|
|
813
|
+
routeResolver: (input) => resolveNativeOnlyDefinitionRoute(input, "restore", { suffix: "/restore" }),
|
|
814
|
+
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
815
|
+
queryParams: { environment_id: "environment_id" },
|
|
816
|
+
bodyBuilder: (input) => {
|
|
817
|
+
const body = input.body ?? {};
|
|
818
|
+
return {
|
|
819
|
+
...(body.comment !== undefined ? { comment: body.comment } : {}),
|
|
820
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
821
|
+
};
|
|
822
|
+
},
|
|
823
|
+
responseExtractor: fmeActionExtract,
|
|
824
|
+
actionDescription: "Restore a killed feature flag definition in an environment (org_id+project_id only). Requires feature_flag_name and environment_id. Optional body comment/title. Same action as fme_feature_flag restore.",
|
|
825
|
+
bodySchema: fmeFeatureFlagKillRestoreReallocateSchema,
|
|
826
|
+
},
|
|
827
|
+
reallocate: {
|
|
828
|
+
method: "POST",
|
|
829
|
+
path: "",
|
|
830
|
+
routeResolver: (input) => resolveNativeOnlyDefinitionRoute(input, "reallocate", { suffix: "/reallocate" }),
|
|
831
|
+
operationPolicy: { risk: "high_write", retryPolicy: "do_not_retry" },
|
|
832
|
+
queryParams: { environment_id: "environment_id" },
|
|
833
|
+
bodyBuilder: (input) => {
|
|
834
|
+
const body = input.body ?? {};
|
|
835
|
+
return {
|
|
836
|
+
...(body.comment !== undefined ? { comment: body.comment } : {}),
|
|
837
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
838
|
+
};
|
|
839
|
+
},
|
|
840
|
+
responseExtractor: fmeActionExtract,
|
|
841
|
+
actionDescription: "Reallocate traffic across treatments for a feature flag definition in an environment (org_id+project_id only). Requires feature_flag_name and environment_id. Optional body comment/title. Same action as fme_feature_flag reallocate.",
|
|
842
|
+
bodySchema: fmeFeatureFlagKillRestoreReallocateSchema,
|
|
360
843
|
},
|
|
361
844
|
},
|
|
362
845
|
},
|
|
363
846
|
{
|
|
364
847
|
resourceType: "fme_rollout_status",
|
|
365
848
|
displayName: "FME Rollout Status",
|
|
366
|
-
description: "Rollout status definitions
|
|
849
|
+
description: "Rollout status definitions (e.g. Killed, Permanent, Ramping). Dual-mode: pass org_id+project_id (preferred) or the deprecated workspace_id. Use harness_list to discover rollout_status_id UUIDs for filtering fme_feature_flag lists. Pagination uses offset/limit (max 100; harness_list size maps to limit).",
|
|
367
850
|
toolset: "feature-flags",
|
|
368
851
|
scope: "account",
|
|
852
|
+
scopeOptional: true,
|
|
369
853
|
identifierFields: ["workspace_id"],
|
|
370
854
|
product: "fme",
|
|
371
855
|
listFilterFields: [
|
|
372
|
-
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace)
|
|
856
|
+
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace). Deprecated — omit and pass org_id+project_id instead for Harness-native scoping." },
|
|
857
|
+
{ name: "offset", description: "Harness-native pagination offset (default 0)", type: "number" },
|
|
858
|
+
{ name: "limit", description: "Harness-native page size (default 100, max 100)", type: "number" },
|
|
373
859
|
],
|
|
374
860
|
operations: {
|
|
375
861
|
list: {
|
|
376
862
|
method: "GET",
|
|
377
|
-
path: "
|
|
863
|
+
path: "",
|
|
864
|
+
routeResolver: (input) => {
|
|
865
|
+
const mode = resolveFmeDualMode(input, "fme_rollout_status");
|
|
866
|
+
if (mode.mode === "legacy") {
|
|
867
|
+
return { path: `/internal/api/v2/rolloutStatuses/ws/${encodeURIComponent(mode.workspaceId)}` };
|
|
868
|
+
}
|
|
869
|
+
return { path: "/fme/api/v4/rollout-statuses", product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
870
|
+
},
|
|
378
871
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
379
|
-
|
|
380
|
-
responseExtractor:
|
|
381
|
-
description: "List rollout
|
|
872
|
+
queryParams: { offset: "offset", size: "limit", limit: "limit" },
|
|
873
|
+
responseExtractor: fmeV4PaginatedListExtract,
|
|
874
|
+
description: "List rollout statuses. Pass org_id+project_id (preferred) or deprecated workspace_id. Optional offset/limit (harness_list size maps to limit).",
|
|
382
875
|
},
|
|
383
876
|
},
|
|
384
877
|
},
|
|
385
878
|
// ── FME Rule-Based Segments ───────────────────────────────────────────
|
|
386
879
|
{
|
|
387
880
|
resourceType: "fme_rule_based_segment",
|
|
388
|
-
displayName: "FME Rule-Based Segment",
|
|
389
|
-
description: "Rule-based segment in a workspace. Supports list, get, create (requires traffic_type_id), and delete. Create requires traffic_type_id passed via params.",
|
|
881
|
+
displayName: "(Deprecated) FME Rule-Based Segment",
|
|
882
|
+
description: "Deprecated — use fme_segment instead for new integrations. Rule-based segment in a workspace. Supports list, get, create (requires traffic_type_id), and delete via the legacy workspace_id contract only — org_id+project_id (Harness-native) is rejected on every operation here in favor of fme_segment. Create requires traffic_type_id passed via params.",
|
|
390
883
|
toolset: "feature-flags",
|
|
391
884
|
scope: "account",
|
|
885
|
+
scopeOptional: true,
|
|
392
886
|
identifierFields: ["workspace_id", "segment_name"],
|
|
393
887
|
product: "fme",
|
|
394
888
|
listFilterFields: [
|
|
395
|
-
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace)
|
|
889
|
+
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace). This resource only supports the legacy workspace_id contract; use fme_segment for Harness-native org_id+project_id scoping." },
|
|
396
890
|
],
|
|
397
891
|
operations: {
|
|
398
892
|
list: {
|
|
399
893
|
method: "GET",
|
|
400
|
-
path: "
|
|
894
|
+
path: "",
|
|
895
|
+
routeResolver: (input) => {
|
|
896
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment");
|
|
897
|
+
if (mode.mode === "harness_native") {
|
|
898
|
+
throw new Error("fme_rule_based_segment.list: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment instead.");
|
|
899
|
+
}
|
|
900
|
+
return { path: `/internal/api/v2/rule-based-segments/ws/${encodeURIComponent(mode.workspaceId)}` };
|
|
901
|
+
},
|
|
401
902
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
402
|
-
pathParams: { workspace_id: "wsId" },
|
|
403
903
|
responseExtractor: passthrough,
|
|
404
904
|
description: "List all rule-based segments in a workspace",
|
|
405
905
|
},
|
|
406
906
|
get: {
|
|
407
907
|
method: "GET",
|
|
408
|
-
path: "
|
|
908
|
+
path: "",
|
|
909
|
+
routeResolver: (input) => {
|
|
910
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment");
|
|
911
|
+
if (mode.mode === "harness_native") {
|
|
912
|
+
throw new Error("fme_rule_based_segment.get: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment instead.");
|
|
913
|
+
}
|
|
914
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_rule_based_segment"));
|
|
915
|
+
return { path: `/internal/api/v2/rule-based-segments/ws/${encodeURIComponent(mode.workspaceId)}/${segmentName}` };
|
|
916
|
+
},
|
|
409
917
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
410
|
-
pathParams: { workspace_id: "wsId", segment_name: "rbSegmentName" },
|
|
411
918
|
responseExtractor: passthrough,
|
|
412
919
|
description: "Get a rule-based segment by name (workspace-level metadata)",
|
|
413
920
|
},
|
|
414
921
|
create: {
|
|
415
922
|
method: "POST",
|
|
416
|
-
path: "
|
|
923
|
+
path: "",
|
|
924
|
+
routeResolver: (input) => {
|
|
925
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment");
|
|
926
|
+
if (mode.mode === "harness_native") {
|
|
927
|
+
throw new Error("fme_rule_based_segment.create: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment instead (create is not yet implemented there either).");
|
|
928
|
+
}
|
|
929
|
+
return { path: `/internal/api/v2/rule-based-segments/ws/${encodeURIComponent(mode.workspaceId)}/trafficTypes/${encodeURIComponent(requireFmeIdentifier(input, "traffic_type_id", "fme_rule_based_segment"))}` };
|
|
930
|
+
},
|
|
417
931
|
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
418
|
-
pathParams: { workspace_id: "wsId", traffic_type_id: "trafficTypeId" },
|
|
419
932
|
bodyBuilder: (input) => {
|
|
420
933
|
const body = input.body;
|
|
421
934
|
return {
|
|
@@ -429,9 +942,16 @@ export const featureFlagsToolset = {
|
|
|
429
942
|
},
|
|
430
943
|
delete: {
|
|
431
944
|
method: "DELETE",
|
|
432
|
-
path: "
|
|
945
|
+
path: "",
|
|
946
|
+
routeResolver: (input) => {
|
|
947
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment");
|
|
948
|
+
if (mode.mode === "harness_native") {
|
|
949
|
+
throw new Error("fme_rule_based_segment.delete: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment instead.");
|
|
950
|
+
}
|
|
951
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_rule_based_segment"));
|
|
952
|
+
return { path: `/internal/api/v2/rule-based-segments/ws/${encodeURIComponent(mode.workspaceId)}/${segmentName}` };
|
|
953
|
+
},
|
|
433
954
|
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
434
|
-
pathParams: { workspace_id: "wsId", segment_name: "rbSegmentName" },
|
|
435
955
|
responseExtractor: passthrough,
|
|
436
956
|
description: "Delete a rule-based segment from a workspace. Environment-level configs must be removed separately.",
|
|
437
957
|
},
|
|
@@ -439,20 +959,29 @@ export const featureFlagsToolset = {
|
|
|
439
959
|
},
|
|
440
960
|
{
|
|
441
961
|
resourceType: "fme_rule_based_segment_definition",
|
|
442
|
-
displayName: "FME Rule-Based Segment Definition",
|
|
443
|
-
description: "Environment-specific definition of a rule-based segment, including targeting rules, exclusions, and matchers. Supports list (by environment), update, and enable/disable/change_request execute actions.",
|
|
962
|
+
displayName: "(Deprecated) FME Rule-Based Segment Definition",
|
|
963
|
+
description: "Deprecated — use fme_segment_definition instead for new integrations. Environment-specific definition of a rule-based segment, including targeting rules, exclusions, and matchers. Supports list (by environment), update, and enable/disable/change_request execute actions.",
|
|
444
964
|
toolset: "feature-flags",
|
|
445
965
|
scope: "account",
|
|
966
|
+
scopeOptional: true,
|
|
446
967
|
identifierFields: ["workspace_id", "environment_id", "segment_name"],
|
|
447
968
|
product: "fme",
|
|
448
969
|
listFilterFields: [
|
|
449
|
-
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace)
|
|
970
|
+
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace). Deprecated — omit and pass org_id+project_id instead for Harness-native scoping." },
|
|
450
971
|
{ name: "environment_id", description: "FME environment ID (get from fme_environment)", required: true },
|
|
451
972
|
],
|
|
452
973
|
operations: {
|
|
453
974
|
list: {
|
|
454
975
|
method: "GET",
|
|
455
976
|
path: "/internal/api/v2/rule-based-segments/ws/{wsId}/environments/{environmentId}",
|
|
977
|
+
routeResolver: (input) => {
|
|
978
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment_definition");
|
|
979
|
+
if (mode.mode === "harness_native") {
|
|
980
|
+
throw new Error("fme_rule_based_segment_definition.list: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment_definition instead.");
|
|
981
|
+
}
|
|
982
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_rule_based_segment_definition"));
|
|
983
|
+
return { path: `/internal/api/v2/rule-based-segments/ws/${encodeURIComponent(mode.workspaceId)}/environments/${environmentId}` };
|
|
984
|
+
},
|
|
456
985
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
457
986
|
pathParams: { workspace_id: "wsId", environment_id: "environmentId" },
|
|
458
987
|
responseExtractor: passthrough,
|
|
@@ -461,6 +990,15 @@ export const featureFlagsToolset = {
|
|
|
461
990
|
update: {
|
|
462
991
|
method: "PUT",
|
|
463
992
|
path: "/internal/api/v2/rule-based-segments/ws/{wsId}/{rbSegmentName}/environments/{environmentId}",
|
|
993
|
+
routeResolver: (input) => {
|
|
994
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment_definition");
|
|
995
|
+
if (mode.mode === "harness_native") {
|
|
996
|
+
throw new Error("fme_rule_based_segment_definition.update: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment_definition instead.");
|
|
997
|
+
}
|
|
998
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_rule_based_segment_definition"));
|
|
999
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_rule_based_segment_definition"));
|
|
1000
|
+
return { path: `/internal/api/v2/rule-based-segments/ws/${encodeURIComponent(mode.workspaceId)}/${segmentName}/environments/${environmentId}` };
|
|
1001
|
+
},
|
|
464
1002
|
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
465
1003
|
pathParams: { workspace_id: "wsId", segment_name: "rbSegmentName", environment_id: "environmentId" },
|
|
466
1004
|
bodyBuilder: (input) => input.body,
|
|
@@ -473,6 +1011,14 @@ export const featureFlagsToolset = {
|
|
|
473
1011
|
enable: {
|
|
474
1012
|
method: "POST",
|
|
475
1013
|
path: "/internal/api/v2/rule-based-segments/{environmentId}/{rbSegmentName}",
|
|
1014
|
+
routeResolver: (input) => {
|
|
1015
|
+
if (isFmeHarnessNativeSelected(input, "fme_rule_based_segment_definition.enable")) {
|
|
1016
|
+
throw new Error("fme_rule_based_segment_definition.enable: Harness-native (org_id/project_id) mode is not supported on this deprecated resource, and fme_segment_definition has no enable equivalent either — pass environment_id/segment_name (legacy contract) instead.");
|
|
1017
|
+
}
|
|
1018
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_rule_based_segment_definition"));
|
|
1019
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_rule_based_segment_definition"));
|
|
1020
|
+
return { path: `/internal/api/v2/rule-based-segments/${environmentId}/${segmentName}` };
|
|
1021
|
+
},
|
|
476
1022
|
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
477
1023
|
pathParams: { environment_id: "environmentId", segment_name: "rbSegmentName" },
|
|
478
1024
|
bodyBuilder: () => ({}),
|
|
@@ -483,6 +1029,14 @@ export const featureFlagsToolset = {
|
|
|
483
1029
|
disable: {
|
|
484
1030
|
method: "DELETE",
|
|
485
1031
|
path: "/internal/api/v2/rule-based-segments/{environmentId}/{rbSegmentName}",
|
|
1032
|
+
routeResolver: (input) => {
|
|
1033
|
+
if (isFmeHarnessNativeSelected(input, "fme_rule_based_segment_definition.disable")) {
|
|
1034
|
+
throw new Error("fme_rule_based_segment_definition.disable: Harness-native (org_id/project_id) mode is not supported on this deprecated resource, and fme_segment_definition has no disable equivalent either — pass environment_id/segment_name (legacy contract) instead.");
|
|
1035
|
+
}
|
|
1036
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_rule_based_segment_definition"));
|
|
1037
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_rule_based_segment_definition"));
|
|
1038
|
+
return { path: `/internal/api/v2/rule-based-segments/${environmentId}/${segmentName}` };
|
|
1039
|
+
},
|
|
486
1040
|
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
487
1041
|
pathParams: { environment_id: "environmentId", segment_name: "rbSegmentName" },
|
|
488
1042
|
responseExtractor: passthrough,
|
|
@@ -495,6 +1049,14 @@ export const featureFlagsToolset = {
|
|
|
495
1049
|
change_request: {
|
|
496
1050
|
method: "POST",
|
|
497
1051
|
path: "/internal/api/v2/changeRequests/ws/{wsId}/environments/{environmentId}",
|
|
1052
|
+
routeResolver: (input) => {
|
|
1053
|
+
const mode = resolveFmeDualMode(input, "fme_rule_based_segment_definition");
|
|
1054
|
+
if (mode.mode === "harness_native") {
|
|
1055
|
+
throw new Error("fme_rule_based_segment_definition.change_request: Harness-native (org_id/project_id) mode is not supported on this deprecated resource, and fme_segment_definition has no change_request equivalent either — pass workspace_id (deprecated) instead.");
|
|
1056
|
+
}
|
|
1057
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_rule_based_segment_definition"));
|
|
1058
|
+
return { path: `/internal/api/v2/changeRequests/ws/${encodeURIComponent(mode.workspaceId)}/environments/${environmentId}` };
|
|
1059
|
+
},
|
|
498
1060
|
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
499
1061
|
pathParams: { workspace_id: "wsId", environment_id: "environmentId" },
|
|
500
1062
|
bodyBuilder: (input) => ({
|
|
@@ -514,22 +1076,32 @@ export const featureFlagsToolset = {
|
|
|
514
1076
|
{
|
|
515
1077
|
resourceType: "fme_traffic_type",
|
|
516
1078
|
displayName: "FME Traffic Type",
|
|
517
|
-
description: "Traffic type
|
|
1079
|
+
description: "Traffic type (e.g. 'user', 'account'). Dual-mode: pass org_id+project_id (preferred) or the deprecated workspace_id. Use harness_list to discover traffic_type_id / name values for flag and segment create. Pagination uses offset/limit (max 100; harness_list size maps to limit).",
|
|
518
1080
|
toolset: "feature-flags",
|
|
519
1081
|
scope: "account",
|
|
1082
|
+
scopeOptional: true,
|
|
520
1083
|
identifierFields: ["workspace_id"],
|
|
521
1084
|
product: "fme",
|
|
522
1085
|
listFilterFields: [
|
|
523
|
-
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace)
|
|
1086
|
+
{ name: "workspace_id", description: "FME workspace ID (get from fme_workspace). Deprecated — omit and pass org_id+project_id instead for Harness-native scoping." },
|
|
1087
|
+
{ name: "offset", description: "Harness-native pagination offset (default 0)", type: "number" },
|
|
1088
|
+
{ name: "limit", description: "Harness-native page size (default 100, max 100)", type: "number" },
|
|
524
1089
|
],
|
|
525
1090
|
operations: {
|
|
526
1091
|
list: {
|
|
527
1092
|
method: "GET",
|
|
528
|
-
path: "
|
|
1093
|
+
path: "",
|
|
1094
|
+
routeResolver: (input) => {
|
|
1095
|
+
const mode = resolveFmeDualMode(input, "fme_traffic_type");
|
|
1096
|
+
if (mode.mode === "legacy") {
|
|
1097
|
+
return { path: `/internal/api/v2/trafficTypes/ws/${encodeURIComponent(mode.workspaceId)}` };
|
|
1098
|
+
}
|
|
1099
|
+
return { path: "/fme/api/v4/traffic-types", product: "harness", scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS };
|
|
1100
|
+
},
|
|
529
1101
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
530
|
-
|
|
531
|
-
responseExtractor:
|
|
532
|
-
description: "List traffic types
|
|
1102
|
+
queryParams: { offset: "offset", size: "limit", limit: "limit" },
|
|
1103
|
+
responseExtractor: fmeV4PaginatedListExtract,
|
|
1104
|
+
description: "List traffic types. Pass org_id+project_id (preferred) or deprecated workspace_id. Optional offset/limit (harness_list size maps to limit).",
|
|
533
1105
|
},
|
|
534
1106
|
},
|
|
535
1107
|
},
|
|
@@ -540,14 +1112,29 @@ export const featureFlagsToolset = {
|
|
|
540
1112
|
description: "Identity (target) in an environment. Create or update identities to manage display name aliases and custom attributes. Requires traffic_type_id and environment_id. Note: the Split Admin API does not support listing or getting individual identities — use create (batch upsert) and update (PATCH single key).",
|
|
541
1113
|
toolset: "feature-flags",
|
|
542
1114
|
scope: "account",
|
|
1115
|
+
scopeOptional: true,
|
|
543
1116
|
identifierFields: ["traffic_type_id", "environment_id", "key"],
|
|
544
1117
|
product: "fme",
|
|
545
1118
|
operations: {
|
|
546
1119
|
create: {
|
|
547
1120
|
method: "POST",
|
|
548
1121
|
path: "/internal/api/v2/trafficTypes/{trafficTypeId}/environments/{environmentId}/identities",
|
|
1122
|
+
routeResolver: (input) => {
|
|
1123
|
+
if (isFmeHarnessNativeSelected(input, "fme_identity.create")) {
|
|
1124
|
+
throw new Error("fme_identity.create: Harness-native (org_id/project_id) mode not yet implemented for this operation — pass traffic_type_id/environment_id (current contract) instead.");
|
|
1125
|
+
}
|
|
1126
|
+
const trafficTypeId = encodeURIComponent(requireFmeIdentifier(input, "traffic_type_id", "fme_identity"));
|
|
1127
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_identity"));
|
|
1128
|
+
return { path: `/internal/api/v2/trafficTypes/${trafficTypeId}/environments/${environmentId}/identities` };
|
|
1129
|
+
},
|
|
549
1130
|
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
550
1131
|
pathParams: { traffic_type_id: "trafficTypeId", environment_id: "environmentId" },
|
|
1132
|
+
paramsSchema: {
|
|
1133
|
+
fields: [
|
|
1134
|
+
{ name: "org_id", required: false, description: "Optional — pass together with project_id to select the (not yet implemented) Harness-native mode instead of the current contract." },
|
|
1135
|
+
{ name: "project_id", required: false, description: "Optional — pass together with org_id to select the (not yet implemented) Harness-native mode instead of the current contract." },
|
|
1136
|
+
],
|
|
1137
|
+
},
|
|
551
1138
|
skipScopeBodyInjection: true,
|
|
552
1139
|
bodyBuilder: (input) => {
|
|
553
1140
|
const body = input.body;
|
|
@@ -572,8 +1159,23 @@ export const featureFlagsToolset = {
|
|
|
572
1159
|
update: {
|
|
573
1160
|
method: "PATCH",
|
|
574
1161
|
path: "/internal/api/v2/trafficTypes/{trafficTypeId}/environments/{environmentId}/identities/{key}",
|
|
1162
|
+
routeResolver: (input) => {
|
|
1163
|
+
if (isFmeHarnessNativeSelected(input, "fme_identity.update")) {
|
|
1164
|
+
throw new Error("fme_identity.update: Harness-native (org_id/project_id) mode not yet implemented for this operation — pass traffic_type_id/environment_id (current contract) instead.");
|
|
1165
|
+
}
|
|
1166
|
+
const trafficTypeId = encodeURIComponent(requireFmeIdentifier(input, "traffic_type_id", "fme_identity"));
|
|
1167
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_identity"));
|
|
1168
|
+
const key = encodeURIComponent(requireFmeIdentifier(input, "key", "fme_identity"));
|
|
1169
|
+
return { path: `/internal/api/v2/trafficTypes/${trafficTypeId}/environments/${environmentId}/identities/${key}` };
|
|
1170
|
+
},
|
|
575
1171
|
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
576
1172
|
pathParams: { traffic_type_id: "trafficTypeId", environment_id: "environmentId", key: "key" },
|
|
1173
|
+
paramsSchema: {
|
|
1174
|
+
fields: [
|
|
1175
|
+
{ name: "org_id", required: false, description: "Optional — pass together with project_id to select the (not yet implemented) Harness-native mode instead of the current contract." },
|
|
1176
|
+
{ name: "project_id", required: false, description: "Optional — pass together with org_id to select the (not yet implemented) Harness-native mode instead of the current contract." },
|
|
1177
|
+
],
|
|
1178
|
+
},
|
|
577
1179
|
bodyBuilder: (input) => input.body,
|
|
578
1180
|
responseExtractor: passthrough,
|
|
579
1181
|
bodySchema: fmeIdentityUpdateSchema,
|
|
@@ -584,51 +1186,244 @@ export const featureFlagsToolset = {
|
|
|
584
1186
|
// ── FME Standard Segments ─────────────────────────────────────────────
|
|
585
1187
|
{
|
|
586
1188
|
resourceType: "fme_standard_segment",
|
|
587
|
-
displayName: "FME Standard Segment",
|
|
588
|
-
description: "Standard (static list) segment in a workspace. List all segments to see names, descriptions, and member counts. For member management, use fme_segment_keys.",
|
|
1189
|
+
displayName: "(Deprecated) FME Standard Segment",
|
|
1190
|
+
description: "Deprecated — use fme_segment instead for new integrations. Standard (static list) segment in a workspace. List all segments to see names, descriptions, and member counts, via the legacy workspace_id contract only — org_id+project_id (Harness-native) is rejected on every operation here in favor of fme_segment. For member management, use fme_segment_keys.",
|
|
589
1191
|
toolset: "feature-flags",
|
|
590
1192
|
scope: "account",
|
|
1193
|
+
scopeOptional: true,
|
|
591
1194
|
identifierFields: ["workspace_id", "segment_name"],
|
|
592
1195
|
product: "fme",
|
|
593
1196
|
listFilterFields: [
|
|
594
|
-
{ name: "workspace_id", description: "Workspace ID (get from fme_workspace)
|
|
1197
|
+
{ name: "workspace_id", description: "Workspace ID (get from fme_workspace). This resource only supports the legacy workspace_id contract; use fme_segment for Harness-native org_id+project_id scoping." },
|
|
595
1198
|
],
|
|
596
1199
|
operations: {
|
|
597
1200
|
list: {
|
|
598
1201
|
method: "GET",
|
|
599
|
-
path: "
|
|
1202
|
+
path: "",
|
|
1203
|
+
routeResolver: (input) => {
|
|
1204
|
+
const mode = resolveFmeDualMode(input, "fme_standard_segment");
|
|
1205
|
+
if (mode.mode === "harness_native") {
|
|
1206
|
+
throw new Error("fme_standard_segment.list: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment instead.");
|
|
1207
|
+
}
|
|
1208
|
+
return { path: `/internal/api/v2/segments/ws/${encodeURIComponent(mode.workspaceId)}` };
|
|
1209
|
+
},
|
|
600
1210
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
601
|
-
pathParams: { workspace_id: "wsId" },
|
|
602
1211
|
responseExtractor: passthrough,
|
|
603
1212
|
description: "List all standard segments in a workspace. Returns segment name, description, and creation metadata.",
|
|
604
1213
|
},
|
|
605
1214
|
get: {
|
|
606
1215
|
method: "GET",
|
|
607
|
-
path: "
|
|
1216
|
+
path: "",
|
|
1217
|
+
routeResolver: (input) => {
|
|
1218
|
+
const mode = resolveFmeDualMode(input, "fme_standard_segment");
|
|
1219
|
+
if (mode.mode === "harness_native") {
|
|
1220
|
+
throw new Error("fme_standard_segment.get: Harness-native (org_id/project_id) mode is not supported on this deprecated resource — use fme_segment instead.");
|
|
1221
|
+
}
|
|
1222
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_standard_segment"));
|
|
1223
|
+
return { path: `/internal/api/v2/segments/ws/${encodeURIComponent(mode.workspaceId)}/${segmentName}` };
|
|
1224
|
+
},
|
|
608
1225
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
609
|
-
pathParams: { workspace_id: "wsId", segment_name: "segmentName" },
|
|
610
1226
|
responseExtractor: passthrough,
|
|
611
1227
|
description: "Get a standard segment's metadata by name.",
|
|
612
1228
|
},
|
|
613
1229
|
},
|
|
614
1230
|
},
|
|
1231
|
+
{
|
|
1232
|
+
resourceType: "fme_segment",
|
|
1233
|
+
displayName: "FME Segment",
|
|
1234
|
+
description: "FME (Harness-native, org_id+project_id scoped). Unified segment type (standard, rule-based, and large). Supports list, get, create, delete. create requires body.type (\"standard\" | \"rule_based\" | \"large\").",
|
|
1235
|
+
toolset: "feature-flags",
|
|
1236
|
+
scope: "project",
|
|
1237
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
1238
|
+
identifierFields: ["segment_name"],
|
|
1239
|
+
operations: {
|
|
1240
|
+
list: {
|
|
1241
|
+
method: "GET",
|
|
1242
|
+
path: "",
|
|
1243
|
+
routeResolver: (input) => {
|
|
1244
|
+
requireHarnessNativeSegmentScope(input, "fme_segment");
|
|
1245
|
+
return { path: "/fme/api/v4/segments" };
|
|
1246
|
+
},
|
|
1247
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
1248
|
+
responseExtractor: passthrough,
|
|
1249
|
+
description: "List all segments (standard and rule-based) in project.",
|
|
1250
|
+
},
|
|
1251
|
+
get: {
|
|
1252
|
+
method: "GET",
|
|
1253
|
+
path: "",
|
|
1254
|
+
routeResolver: (input) => {
|
|
1255
|
+
requireHarnessNativeSegmentScope(input, "fme_segment");
|
|
1256
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment"));
|
|
1257
|
+
return { path: `/fme/api/v4/segments/${segmentName}` };
|
|
1258
|
+
},
|
|
1259
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
1260
|
+
responseExtractor: passthrough,
|
|
1261
|
+
description: "Get single segment by name.",
|
|
1262
|
+
},
|
|
1263
|
+
delete: {
|
|
1264
|
+
method: "DELETE",
|
|
1265
|
+
path: "",
|
|
1266
|
+
routeResolver: (input) => {
|
|
1267
|
+
requireHarnessNativeSegmentScope(input, "fme_segment");
|
|
1268
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment"));
|
|
1269
|
+
return { path: `/fme/api/v4/segments/${segmentName}` };
|
|
1270
|
+
},
|
|
1271
|
+
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
1272
|
+
responseExtractor: passthrough,
|
|
1273
|
+
description: "Delete a segment by name.",
|
|
1274
|
+
},
|
|
1275
|
+
create: {
|
|
1276
|
+
method: "POST",
|
|
1277
|
+
path: "",
|
|
1278
|
+
routeResolver: (input) => {
|
|
1279
|
+
requireHarnessNativeSegmentScope(input, "fme_segment");
|
|
1280
|
+
return { path: "/fme/api/v4/segments" };
|
|
1281
|
+
},
|
|
1282
|
+
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
1283
|
+
bodyBuilder: (input) => {
|
|
1284
|
+
const body = input.body;
|
|
1285
|
+
const trafficType = (body?.trafficType ?? body?.traffic_type);
|
|
1286
|
+
if (body?.type !== undefined && !FME_SEGMENT_TYPES.includes(body.type)) {
|
|
1287
|
+
throw new Error(`fme_segment.create: invalid type '${body.type}'. Must be one of: ${FME_SEGMENT_TYPES.join(", ")}.`);
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
name: body?.name,
|
|
1291
|
+
trafficType,
|
|
1292
|
+
type: body?.type,
|
|
1293
|
+
...(body?.description !== undefined ? { description: body.description } : {}),
|
|
1294
|
+
...(body?.tags !== undefined ? { tags: normalizeFmeTags(body.tags) } : {}),
|
|
1295
|
+
...(body?.owners !== undefined ? { owners: body.owners } : {}),
|
|
1296
|
+
};
|
|
1297
|
+
},
|
|
1298
|
+
responseExtractor: passthrough,
|
|
1299
|
+
bodySchema: fmeSegmentCreateSchema,
|
|
1300
|
+
description: "Create a new segment. Body requires name + trafficType + type (\"standard\" | \"rule_based\" | \"large\"); optional description, tags, owners.",
|
|
1301
|
+
},
|
|
1302
|
+
},
|
|
1303
|
+
},
|
|
1304
|
+
// ── FME Segment Definition (Harness-native, unified — Harness_Split/Main PR #12644) ──
|
|
1305
|
+
{
|
|
1306
|
+
resourceType: "fme_segment_definition",
|
|
1307
|
+
displayName: "FME Segment Definition",
|
|
1308
|
+
description: "Environment-specific definition of a segment (standard or rule-based) — description and lifecycle. Replaces fme_rule_based_segment_definition's role in Harness-native calls, generalized for all segment types. Harness-native only (org_id+project_id; no legacy workspace_id support). Supports list, get, create, update (description only, via JSON Merge Patch), and delete. Wired against Harness_Split/Main PR #12644, which was open (not yet merged) as of this writing — paths may still change before merge. There is no enable/disable/change_request action: the backend has no such endpoints for this unified resource; governance checks are surfaced inline in the create/update/delete responses instead.",
|
|
1309
|
+
toolset: "feature-flags",
|
|
1310
|
+
scope: "project",
|
|
1311
|
+
scopeParams: FME_HARNESS_NATIVE_SCOPE_PARAMS,
|
|
1312
|
+
identifierFields: ["segment_name", "environment_id"],
|
|
1313
|
+
listFilterFields: [
|
|
1314
|
+
{ name: "environment_id", description: "FME environment ID (get from fme_environment)", required: true },
|
|
1315
|
+
{ name: "status", description: "Filter by definition status", enum: ["ACTIVE", "ARCHIVED"] },
|
|
1316
|
+
{ name: "offset", description: "Pagination offset", type: "number" },
|
|
1317
|
+
{ name: "limit", description: "Page size (max 100, default 100)", type: "number" },
|
|
1318
|
+
],
|
|
1319
|
+
operations: {
|
|
1320
|
+
list: {
|
|
1321
|
+
method: "GET",
|
|
1322
|
+
path: "",
|
|
1323
|
+
routeResolver: (input) => {
|
|
1324
|
+
requireHarnessNativeSegmentScope(input, "fme_segment_definition");
|
|
1325
|
+
return { path: "/fme/api/v4/segment-definitions" };
|
|
1326
|
+
},
|
|
1327
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
1328
|
+
queryParams: { environment_id: "environment_id", status: "status", offset: "offset", limit: "limit" },
|
|
1329
|
+
responseExtractor: passthrough,
|
|
1330
|
+
description: "List segment definitions in an environment, with pagination (offset/limit, max 100) and an optional status filter.",
|
|
1331
|
+
},
|
|
1332
|
+
get: {
|
|
1333
|
+
method: "GET",
|
|
1334
|
+
path: "",
|
|
1335
|
+
routeResolver: (input) => {
|
|
1336
|
+
requireHarnessNativeSegmentScope(input, "fme_segment_definition");
|
|
1337
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment_definition"));
|
|
1338
|
+
return { path: `/fme/api/v4/segment-definitions/${segmentName}` };
|
|
1339
|
+
},
|
|
1340
|
+
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
1341
|
+
queryParams: { environment_id: "environment_id" },
|
|
1342
|
+
responseExtractor: passthrough,
|
|
1343
|
+
description: "Get a single segment definition by name in an environment.",
|
|
1344
|
+
},
|
|
1345
|
+
create: {
|
|
1346
|
+
method: "POST",
|
|
1347
|
+
path: "",
|
|
1348
|
+
routeResolver: (input) => {
|
|
1349
|
+
requireHarnessNativeSegmentScope(input, "fme_segment_definition");
|
|
1350
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment_definition"));
|
|
1351
|
+
return { path: `/fme/api/v4/segment-definitions/${segmentName}` };
|
|
1352
|
+
},
|
|
1353
|
+
operationPolicy: { risk: "low_write", retryPolicy: "do_not_retry" },
|
|
1354
|
+
queryParams: { environment_id: "environment_id" },
|
|
1355
|
+
bodyBuilder: (input) => {
|
|
1356
|
+
const body = input.body;
|
|
1357
|
+
return body?.description !== undefined ? { description: body.description } : {};
|
|
1358
|
+
},
|
|
1359
|
+
responseExtractor: passthrough,
|
|
1360
|
+
bodySchema: fmeSegmentDefinitionCreateSchema,
|
|
1361
|
+
description: "Create a segment definition in an environment. Omit the description (or send an empty body) to create an empty/default shell.",
|
|
1362
|
+
},
|
|
1363
|
+
update: {
|
|
1364
|
+
method: "PATCH",
|
|
1365
|
+
path: "",
|
|
1366
|
+
routeResolver: (input) => {
|
|
1367
|
+
requireHarnessNativeSegmentScope(input, "fme_segment_definition");
|
|
1368
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment_definition"));
|
|
1369
|
+
return { path: `/fme/api/v4/segment-definitions/${segmentName}` };
|
|
1370
|
+
},
|
|
1371
|
+
operationPolicy: { risk: "low_write", retryPolicy: "safe" },
|
|
1372
|
+
queryParams: { environment_id: "environment_id" },
|
|
1373
|
+
headers: { "Content-Type": "application/merge-patch+json" },
|
|
1374
|
+
bodyBuilder: (input) => {
|
|
1375
|
+
const body = input.body;
|
|
1376
|
+
if (!body || !("description" in body))
|
|
1377
|
+
return {};
|
|
1378
|
+
return { description: body.description };
|
|
1379
|
+
},
|
|
1380
|
+
responseExtractor: passthrough,
|
|
1381
|
+
bodySchema: fmeSegmentDefinitionUpdateSchema,
|
|
1382
|
+
description: "Update a segment definition's description via JSON Merge Patch (RFC 7396) — the only mutable field. Omit description to leave it unchanged, pass null to clear it, or a string to set it.",
|
|
1383
|
+
},
|
|
1384
|
+
delete: {
|
|
1385
|
+
method: "DELETE",
|
|
1386
|
+
path: "",
|
|
1387
|
+
routeResolver: (input) => {
|
|
1388
|
+
requireHarnessNativeSegmentScope(input, "fme_segment_definition");
|
|
1389
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment_definition"));
|
|
1390
|
+
return { path: `/fme/api/v4/segment-definitions/${segmentName}` };
|
|
1391
|
+
},
|
|
1392
|
+
operationPolicy: { risk: "destructive", retryPolicy: "do_not_retry" },
|
|
1393
|
+
queryParams: { environment_id: "environment_id" },
|
|
1394
|
+
responseExtractor: passthrough,
|
|
1395
|
+
description: "Delete a segment definition from an environment.",
|
|
1396
|
+
},
|
|
1397
|
+
},
|
|
1398
|
+
},
|
|
615
1399
|
{
|
|
616
1400
|
resourceType: "fme_segment_keys",
|
|
617
1401
|
displayName: "FME Segment Keys",
|
|
618
1402
|
description: "Membership keys (members) of a standard segment. List keys with pagination, or update to add members. Removal is not supported by this endpoint. Limit: 10,000 keys per request, 100,000 per segment total.",
|
|
619
1403
|
toolset: "feature-flags",
|
|
620
1404
|
scope: "account",
|
|
1405
|
+
scopeOptional: true,
|
|
621
1406
|
identifierFields: ["environment_id", "segment_name"],
|
|
622
1407
|
product: "fme",
|
|
623
1408
|
listFilterFields: [
|
|
624
1409
|
{ name: "environment_id", description: "Environment ID (get from fme_environment)", required: true },
|
|
625
1410
|
{ name: "segment_name", description: "Segment name", required: true },
|
|
626
1411
|
{ name: "offset", description: "Pagination offset", type: "number" },
|
|
1412
|
+
{ name: "org_id", description: "Optional — pass together with project_id to select the (not yet implemented) Harness-native mode instead of the current contract." },
|
|
1413
|
+
{ name: "project_id", description: "Optional — pass together with org_id to select the (not yet implemented) Harness-native mode instead of the current contract." },
|
|
627
1414
|
],
|
|
628
1415
|
operations: {
|
|
629
1416
|
list: {
|
|
630
1417
|
method: "GET",
|
|
631
1418
|
path: "/internal/api/v2/segments/{environmentId}/{segmentName}/keys",
|
|
1419
|
+
routeResolver: (input) => {
|
|
1420
|
+
if (isFmeHarnessNativeSelected(input, "fme_segment_keys.list")) {
|
|
1421
|
+
throw new Error("fme_segment_keys.list: Harness-native (org_id/project_id) mode not yet implemented for this operation — pass environment_id/segment_name (current contract) instead.");
|
|
1422
|
+
}
|
|
1423
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_segment_keys"));
|
|
1424
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment_keys"));
|
|
1425
|
+
return { path: `/internal/api/v2/segments/${environmentId}/${segmentName}/keys` };
|
|
1426
|
+
},
|
|
632
1427
|
operationPolicy: { risk: "read", retryPolicy: "safe" },
|
|
633
1428
|
pathParams: { environment_id: "environmentId", segment_name: "segmentName" },
|
|
634
1429
|
queryParams: {
|
|
@@ -641,6 +1436,14 @@ export const featureFlagsToolset = {
|
|
|
641
1436
|
update: {
|
|
642
1437
|
method: "PUT",
|
|
643
1438
|
path: "/internal/api/v2/segments/{environmentId}/{segmentName}/upload",
|
|
1439
|
+
routeResolver: (input) => {
|
|
1440
|
+
if (isFmeHarnessNativeSelected(input, "fme_segment_keys.update")) {
|
|
1441
|
+
throw new Error("fme_segment_keys.update: Harness-native (org_id/project_id) mode not yet implemented for this operation — pass environment_id/segment_name (current contract) instead.");
|
|
1442
|
+
}
|
|
1443
|
+
const environmentId = encodeURIComponent(requireFmeIdentifier(input, "environment_id", "fme_segment_keys"));
|
|
1444
|
+
const segmentName = encodeURIComponent(requireFmeIdentifier(input, "segment_name", "fme_segment_keys"));
|
|
1445
|
+
return { path: `/internal/api/v2/segments/${environmentId}/${segmentName}/upload` };
|
|
1446
|
+
},
|
|
644
1447
|
operationPolicy: { risk: "medium_write", retryPolicy: "do_not_retry" },
|
|
645
1448
|
pathParams: { environment_id: "environmentId", segment_name: "segmentName" },
|
|
646
1449
|
skipScopeBodyInjection: true,
|